Skip to content
Open
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
8 changes: 8 additions & 0 deletions src/EmbedResourceCSharp/DiagnosticsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,12 @@ internal sealed class DiagnosticsHelper
category: "ResourceEmbedCSharp",
DiagnosticSeverity.Error,
true);

internal static readonly DiagnosticDescriptor EncodingNotFoundError = new(
id: "EMBED003",
title: "Encoding Not Found",
messageFormat: "Encoding '{0}' is not a supported encoding name",
category: "ResourceEmbedCSharp",
DiagnosticSeverity.Error,
true);
}
109 changes: 107 additions & 2 deletions src/EmbedResourceCSharp/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
return compilation.GetTypeByMetadataName("EmbedResourceCSharp.FileEmbedAttribute");
})
.WithComparer(SymbolEqualityComparer.Default);
var str = context.CompilationProvider
.Select(static (compilation, token) =>
{
token.ThrowIfCancellationRequested();
return compilation.GetTypeByMetadataName("EmbedResourceCSharp.StringEmbedAttribute");
})
.WithComparer(SymbolEqualityComparer.Default);
var folder = context.CompilationProvider
.Select(static (compilation, token) =>
{
Expand All @@ -37,15 +44,20 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
.CreateSyntaxProvider(Predicate, Transform)
.Combine(file)
.Select(PostTransformFile)
.Where(x => x.Method is not null && x.Path is not null)!
.WithComparer(FileAttributeComparer.Instance);
.Where(x => x.Method is not null && x.Path is not null)!;
var strings = context.SyntaxProvider
.CreateSyntaxProvider(Predicate, Transform)
.Combine(str)
.Select(PostTransformString)
.Where(x => x.Method is not null && x.Path is not null);
var folders = context.SyntaxProvider
.CreateSyntaxProvider(Predicate, Transform)
.Combine(folder)
.Select(PostTransform)
.Where(x => x.Method is not null);

context.RegisterSourceOutput(files.Combine(options), GenerateFileEmbed);
context.RegisterSourceOutput(strings.Combine(options), GenerateStringEmbed);
context.RegisterSourceOutput(folders.Combine(options), GenerateFolderEmbed!);
}

Expand Down Expand Up @@ -95,6 +107,36 @@ private bool Predicate(SyntaxNode node, CancellationToken token)
return default;
}

private (IMethodSymbol? Method, string? Path, string? EncodingName) PostTransformString((IMethodSymbol? Method, INamedTypeSymbol? Type) pair, CancellationToken token)
{
var type = pair.Type;
if (type is null)
{
return default;
}

var method = pair.Method;
if (method is null)
{
return default;
}

foreach (var attribute in method.GetAttributes())
{
token.ThrowIfCancellationRequested();
if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, type))
{
var path = attribute.ConstructorArguments[0].Value as string;
var encodingName = attribute.ConstructorArguments.Length == 2
? attribute.ConstructorArguments[1].Value as string
: null;
return (method, path, encodingName);
}
}

return default;
}

private (IMethodSymbol? Method, AttributeData? Data) PostTransform((IMethodSymbol? Method, INamedTypeSymbol? Type) pair, CancellationToken token)
{
var type = pair.Type;
Expand Down Expand Up @@ -207,6 +249,69 @@ private void GenerateFileEmbed(SourceProductionContext context, ((IMethodSymbol
context.AddSource(hintName, source);
}

private void GenerateStringEmbed(SourceProductionContext context, ((IMethodSymbol Method, string Path, string? EncodingName) Left, Options Options) pair)
{
if (string.IsNullOrWhiteSpace(pair.Options.ProjectDir))
{
return;
}

StringBuilder builder;

var token = context.CancellationToken;
token.ThrowIfCancellationRequested();
var method = pair.Left.Method;
var path = pair.Left.Path;
var encodingName = pair.Left.EncodingName;

var filePath = Path.Combine(pair.Options.ProjectDir, path);
if (!File.Exists(filePath))
{
var location = Location.None;
if (method.AssociatedSymbol is { Locations: { Length: > 0 } locations })
{
location = locations[0];
}

context.ReportDiagnostic(Diagnostic.Create(DiagnosticsHelper.FileNotFoundError, location, filePath));
return;
}

Encoding? encoding = null;
if (encodingName is not null)
{
try
{
encoding = Encoding.GetEncoding(encodingName);
}
catch (ArgumentException)
{
var location = Location.None;
if (method.AssociatedSymbol is { Locations: { Length: > 0 } locations })
{
location = locations[0];
}

context.ReportDiagnostic(Diagnostic.Create(DiagnosticsHelper.EncodingNotFoundError, location, encodingName));
return;
}
}

builder = new StringBuilder();
if (pair.Options.IsDesignTimeBuild)
{
Utility.ProcessFileDesignTimeBuild(builder, method);
}
else
{
Utility.ProcessString(builder, method, filePath, encoding, token);
}

var source = builder.ToString();
var hintName = Utility.CalcHintName(builder, method, ".string.g.cs");
context.AddSource(hintName, source);
}

private sealed class FileAttributeComparer : IEqualityComparer<ValueTuple<IMethodSymbol, string>>
{
public static readonly FileAttributeComparer Instance = new();
Expand Down
34 changes: 33 additions & 1 deletion src/EmbedResourceCSharp/Utility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,24 @@ public FileEmbedAttribute(string path)
}
}

[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false)]
internal sealed class StringEmbedAttribute : global::System.Attribute
{
public string Path { get; }
public string? EncodingName { get; }

public StringEmbedAttribute(string path)
{
Path = path;
}

public StringEmbedAttribute(string path, string encodingName)
{
Path = path;
EncodingName = encodingName;
}
}

[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false)]
internal sealed class FolderEmbedAttribute : global::System.Attribute
{
Expand Down Expand Up @@ -210,6 +228,20 @@ public static void ProcessFile(StringBuilder buffer, IMethodSymbol method, strin
Footer(buffer);
}

public static void ProcessString(StringBuilder buffer, IMethodSymbol method, string filePath, Encoding? encoding, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var content = encoding is null ? File.ReadAllText(filePath) : File.ReadAllText(filePath, encoding);
Header(buffer, method);
buffer.Append("()").AppendLine();
buffer.Append(" {").AppendLine();
buffer.Append(" return \"\"\"\"\"\"").AppendLine();
buffer.Append(content).AppendLine();
buffer.Append("\"\"\"\"\"\";").AppendLine();
buffer.Append(" }");
Footer(buffer);
}

private static void Footer(StringBuilder buffer)
{
buffer.AppendLine().Append(" }").AppendLine().Append('}').AppendLine().AppendLine();
Expand Down Expand Up @@ -241,7 +273,7 @@ private static void Header(StringBuilder buffer, IMethodSymbol method)
buffer.Append(" {").AppendLine();
buffer.Append(" ");
PrintAccessibility(buffer, method.DeclaredAccessibility);
buffer.Append(" static partial global::System.ReadOnlySpan<byte> ");
buffer.Append($" static partial {method.ReturnType.ToDisplayString()} ");
buffer.Append(method.Name);
}

Expand Down
15 changes: 15 additions & 0 deletions tests/FileTests/EmbedTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using EmbedResourceCSharp;
using Xunit;

Expand All @@ -9,11 +10,18 @@ namespace FileTests
public partial class EmbedTests
{
private static string GetCurrentFilePath([CallerFilePath] string path = "") => path;

private readonly string currentFolder;

[FileEmbed("a.txt")]
private static partial ReadOnlySpan<byte> GetA();

[StringEmbed("a.txt")]
private static partial string GetStringDefaultEncoding();

[StringEmbed("a.txt", "utf-8")]
private static partial string GetStringUtf8();

public EmbedTests()
{
currentFolder = Path.GetDirectoryName(GetCurrentFilePath()) ?? "";
Expand All @@ -26,6 +34,13 @@ public void FileEmbedTest()
Assert.True(GetA().SequenceEqual(original.AsSpan()));
}

[Fact]
public void StringEmbedTest()
{
var original = File.ReadAllText(Path.Combine(currentFolder, "./a.txt"));
Assert.Equal(original, GetStringDefaultEncoding());
}

[Fact]
public void FileNotFoundTest()
{
Expand Down