Skip to content

Commit

Permalink
Added support for generating consts and custom identifier names
Browse files Browse the repository at this point in the history
  • Loading branch information
yugabe committed Jul 17, 2024
1 parent a6fdc29 commit 10d3fcb
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 14 deletions.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,22 @@ Whether you have the generator automatically generating or not, you can explicit

### Configuring the generated class name and namespace

You can set the `PodNet_EmbedTextNamespace` and `PodNet_EmbedTextClassName` properties on the items to override the default namespace and class name.
You can set the `PodNet_EmbedTextNamespace`, `PodNet_EmbedTextClassName`, `PodNet_EmbeddedTextIsConst` and `PodNet_EmbeddedTextIdentifier` properties (attributes) on the items to override the default namespace, class and identifier name, as well as to generate a const instead of a property.

```csproj
<Project>
<!-- Additional properties, items and targets omitted -->
<ItemGroup>
<!-- The default namespace for the file would be "MyProject.Files" and the class would be "My_File_txt". -->
<AdditionalFiles Include="Files/My File.txt" PodNet_EmbedTextNamespace="OtherNamespace" PodNet_EmbedTextClassName="MyFileTXT" />
<!-- The defaults would be:
- Namespace: "MyProject.Files", generated from the directory structure and the project root namespace,
- ClassName: "My_File_txt", generated from sanitizing the file name,
- IsConst: unless set to true, the generated member is a property that returns the constant value by expression body,
- Identifier: defaults to "Content". -->
<AdditionalFiles Include="Files/My File.txt"
PodNet_EmbedTextNamespace="OtherNamespace"
PodNet_EmbedTextClassName="MyFileTXT"
PodNet_EmbedTextIsConst="true"
PodNet_EmbedTextIdentifier="Text" />
</ItemGroup>
</Project>
```
Expand Down
29 changes: 23 additions & 6 deletions src/EmbeddedTexts/EmbeddedTextsGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ public sealed class EmbeddedTextsGenerator : IIncrementalGenerator
public const string EmbedTextMetadataProperty = "PodNet_EmbedText";
public const string EmbedTextNamespaceMetadataProperty = "PodNet_EmbedTextNamespace";
public const string EmbedTextClassNameMetadataProperty = "PodNet_EmbedTextClassName";
public const string EmbedTextIsConstMetadataProperty = "PodNet_EmbedTextIsConst";
public const string EmbedTextIdentifierMetadataProperty = "PodNet_EmbedTextIdentifier";

public record EmbeddedTextItemOptions(
string? RootNamespace,
string? ProjectDirectory,
string? ItemNamespace,
string? ItemClassName,
bool? IsConst,
string? Identifier,
bool Enabled,
AdditionalText Text);

Expand All @@ -36,6 +40,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
ProjectDirectory: options.GlobalOptions.GetProjectDirectory(),
ItemNamespace: itemOptions.GetAdditionalTextMetadata(EmbedTextNamespaceMetadataProperty),
ItemClassName: itemOptions.GetAdditionalTextMetadata(EmbedTextClassNameMetadataProperty),
IsConst: string.Equals(itemOptions.GetAdditionalTextMetadata(EmbedTextIsConstMetadataProperty), "true", StringComparison.OrdinalIgnoreCase),
Identifier: itemOptions.GetAdditionalTextMetadata(EmbedTextIdentifierMetadataProperty),
Enabled: (globalEnabled || itemEnabled) && !itemDisabled,
Text: text);
});
Expand All @@ -52,17 +58,24 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
if (item.Text.Path is not { Length: > 0 })
throw new InvalidOperationException("Path not found for file.");

var className = TextProcessing.GetClassName(item.ItemClassName is { Length: > 0 }
? item.ItemClassName
: Path.GetFileName(item.Text.Path));

var relativeFolderPath = PathProcessing.GetRelativePath(item.ProjectDirectory, Path.GetDirectoryName(item.Text.Path));
var relativeFilePath = PathProcessing.GetRelativePath(item.ProjectDirectory, item.Text.Path);

var @namespace = TextProcessing.GetNamespace(item.ItemNamespace is { Length: > 0 }
? item.ItemNamespace
: $"{item.RootNamespace}.{relativeFolderPath}");

var className = TextProcessing.GetClassName(item.ItemClassName is { Length: > 0 }
? item.ItemClassName
: Path.GetFileName(item.Text.Path));

var isConst = item.IsConst is true;
var modifier = isConst ? "const" : "static";

var identifierName = TextProcessing.GetClassName(item.Identifier is { Length: > 0 }
? item.Identifier
: "Content");

var separator = new string('"', 3);
while (lines.Any(l => l.Text?.ToString().Contains(separator) == true))
separator += '\"';
Expand All @@ -81,17 +94,21 @@ public static partial class {{className}}
/// <code>
""");

foreach (var line in lines)
foreach (var line in lines.Take(10))
{
sourceBuilder.AppendLine($$"""
/// {{line.ToString().Replace("<", "&lt;").Replace(">", "&gt;")}}
""");
}
if (lines.Count > 10)
{
sourceBuilder.AppendLine($"/// [{lines.Count - 10} more lines ({lines.Count} total)] ");
}

sourceBuilder.AppendLine($$"""
/// </code>
/// </summary>
public static string Content { get; } = {{separator}}
public {{modifier}} string {{identifierName}} {{(isConst ? "=" : "=>")}} {{separator}}
{{text}}
{{separator}};
}
Expand Down
28 changes: 23 additions & 5 deletions tests/EmbeddedTexts.Tests/EmbeddedTextGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,24 @@ public void DoesntGenerateWhenDisabled()
public void GeneratesForOptInOnly()
{
var result = RunGeneration(false, true);
// This is a complex assertion that makes sure there is a single result that contains a single property declaration with the expected content in its initializer. Doesn't check for other structural correctness.
Assert.AreEqual("Test File 2 Content", ((LiteralExpressionSyntax)result.Results.Single().GeneratedSources.Single().SyntaxTree.GetRoot().DescendantNodes().OfType<PropertyDeclarationSyntax>().Single().Initializer!.Value).ToString().Trim('"').Trim());

Assert.IsTrue(result.Results.Single().GeneratedSources.Single().SyntaxTree.ToString().Contains("Test File 2 Content"));
}

[TestMethod]
public void DoesntGenerateForOptOut()
{
var result = RunGeneration(true, false);
Assert.AreEqual(1, result.Results.Length);
Assert.AreEqual(5, result.Results[0].GeneratedSources.Length);
Assert.AreEqual(7, result.Results[0].GeneratedSources.Length);
}

[TestMethod]
public void GenerateAllWhenGloballyEnabledAndItemIsOptIn()
{
var result = RunGeneration(true, true);
Assert.AreEqual(1, result.Results.Length);
Assert.AreEqual(6, result.Results[0].GeneratedSources.Length);
Assert.AreEqual(8, result.Results[0].GeneratedSources.Length);
}

[TestMethod]
Expand All @@ -56,7 +56,7 @@ namespace Project;
public static partial class Parameterized_Enabled_cs
{
public static string Content { get; } = """
public static string Content => """
Test File 2 Content
""";
}
Expand All @@ -83,6 +83,20 @@ public void GeneratesCorrectClassNamesAndNamespaces(string expectedClassName, st
Assert.AreEqual(expectedClassName, className);
}

[DataTestMethod]
[DataRow("Default_txt", "public static string Content => ")]
[DataRow("Const", "public const string Content = ")]
[DataRow("CustomIdentifier", "public static string CustomIdentifier => ")]
public void GeneratesConstantsAndCustomIdentifiersOnDemand(string expectedClassName, string expectedDeclaration)
{
var result = RunGeneration(true, true);

var source = result.Results.Single().GeneratedSources.SingleOrDefault(s => s.HintName.EndsWith($"{expectedClassName}.g.cs"));
Assert.IsNotNull(source);
var actualDeclaration = source.SyntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().SingleOrDefault()?.DescendantNodes().OfType<MemberDeclarationSyntax>().SingleOrDefault()?.ToString();
Assert.AreEqual(expectedDeclaration, actualDeclaration?[0..expectedDeclaration.Length]);
}

public static GeneratorDriverRunResult RunGeneration(bool globalEnabled, bool oneItemEnabled)
{
var additionalTextsLookup = GetOptionsForTexts(oneItemEnabled)
Expand Down Expand Up @@ -117,5 +131,9 @@ public static GeneratorDriverRunResult RunGeneration(bool globalEnabled, bool on
= new() { [$"build_metadata.additionalfiles.{EmbeddedTextsGenerator.EmbedTextClassNameMetadataProperty}"] = "TestClassName" },
[new($@"{ProjectRoot}//Empty", "")] = [],
[new($@"{ProjectRoot}//Subdirectory//2 Another & Subdirectory/Empty.ini", "")] = [],
[new($@"{ProjectRoot}//Const", "")]
= new () { [$"build_metadata.additionalfiles.{EmbeddedTextsGenerator.EmbedTextIsConstMetadataProperty}"] = "true" },
[new($@"{ProjectRoot}//CustomIdentifier", "")]
= new() { [$"build_metadata.additionalfiles.{EmbeddedTextsGenerator.EmbedTextIdentifierMetadataProperty}"] = "CustomIdentifier" },
};
}

0 comments on commit 10d3fcb

Please sign in to comment.