Skip to content

Commit 6875d44

Browse files
committed
Handle library-specific features in embedded mode
Fixes #4
1 parent 2bca7e7 commit 6875d44

11 files changed

+177
-56
lines changed

src/RazorBlade.Analyzers.Tests/EmbeddedLibraryMetaSourceGeneratorTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ private static GeneratorDriverRunResult Generate(string input)
9999

100100
var result = CSharpGeneratorDriver.Create(new EmbeddedLibraryMetaSourceGenerator
101101
{
102-
SkipAddSource = true
102+
SkipGlobal = true
103103
})
104104
.AddAdditionalTexts(ImmutableArray.Create<AdditionalText>(new AdditionalTextMock(input, "./TestFile.cs")))
105105
.WithUpdatedAnalyzerConfigOptions(new AnalyzerConfigOptionsProviderMock

src/RazorBlade.Analyzers.Tests/RazorBladeSourceGeneratorTests.cs

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,34 @@ protected BaseClass(in int foo, ref int bar, out int baz, params int[] qux)
104104
);
105105
}
106106

107+
[Test]
108+
public Task should_forward_constructor_from_embedded_library()
109+
{
110+
return Verify(
111+
"""
112+
@inherits Foo.BaseClass
113+
""",
114+
embeddedLibrary: """
115+
using System;
116+
using RazorBlade.Support;
117+
118+
namespace Foo;
119+
120+
public abstract class BaseClass : RazorBlade.HtmlTemplate
121+
{
122+
protected BaseClass(int notIncluded)
123+
{
124+
}
125+
126+
[TemplateConstructor]
127+
protected BaseClass(int? foo, string? bar)
128+
{
129+
}
130+
}
131+
"""
132+
);
133+
}
134+
107135
[Test]
108136
public Task should_reject_model_directive()
109137
{
@@ -181,43 +209,60 @@ public void OnlyOnSync(double i) {}
181209
);
182210
}
183211

184-
private static GeneratorDriverRunResult Generate(string input, string? csharpCode = null)
212+
private static GeneratorDriverRunResult Generate(string input, string? csharpCode, string? embeddedLibrary)
185213
{
186214
var runtimeDir = Path.GetDirectoryName(typeof(object).Assembly.Location)!;
187215

188216
var compilation = CSharpCompilation.Create("TestAssembly")
189-
.AddReferences(
190-
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
191-
MetadataReference.CreateFromFile(Path.Combine(runtimeDir, "netstandard.dll")),
192-
MetadataReference.CreateFromFile(Path.Combine(runtimeDir, "System.Runtime.dll")),
193-
MetadataReference.CreateFromFile(typeof(RazorTemplate).Assembly.Location)
217+
.AddReferences(new[]
218+
{
219+
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
220+
MetadataReference.CreateFromFile(Path.Combine(runtimeDir, "netstandard.dll")),
221+
MetadataReference.CreateFromFile(Path.Combine(runtimeDir, "System.Runtime.dll")),
222+
embeddedLibrary is null
223+
? MetadataReference.CreateFromFile(typeof(RazorTemplate).Assembly.Location)
224+
: null
225+
}.Where(i => i is not null)!
226+
)
227+
.AddSyntaxTrees(
228+
CSharpSyntaxTree.ParseText(csharpCode ?? string.Empty),
229+
CSharpSyntaxTree.ParseText(embeddedLibrary ?? string.Empty)
194230
)
195-
.AddSyntaxTrees(CSharpSyntaxTree.ParseText(csharpCode ?? string.Empty))
196231
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithNullableContextOptions(NullableContextOptions.Enable));
197232

233+
var analyzerConfigOptionsProvider = new AnalyzerConfigOptionsProviderMock
234+
{
235+
{ "IsRazorBlade", "True" },
236+
{ "Namespace", "TestNamespace" }
237+
};
238+
239+
if (embeddedLibrary is not null)
240+
analyzerConfigOptionsProvider.Add("RazorBladeEmbeddedLibrary", "true");
241+
198242
var result = CSharpGeneratorDriver.Create(new RazorBladeSourceGenerator())
199243
.AddAdditionalTexts(ImmutableArray.Create<AdditionalText>(new AdditionalTextMock(input, "./TestFile.cshtml")))
200-
.WithUpdatedAnalyzerConfigOptions(new AnalyzerConfigOptionsProviderMock
201-
{
202-
{ "IsRazorBlade", "True" },
203-
{ "Namespace", "TestNamespace" }
204-
})
244+
.WithUpdatedAnalyzerConfigOptions(analyzerConfigOptionsProvider)
205245
.RunGeneratorsAndUpdateCompilation(compilation, out var updatedCompilation, out _)
206246
.GetRunResult();
207247

208248
var diagnostics = updatedCompilation.GetDiagnostics();
209249

210-
if (!diagnostics.IsEmpty)
211-
Console.WriteLine(result.GeneratedTrees.FirstOrDefault());
250+
if (embeddedLibrary is null) // Don't validate the embedded library generator here, assume the output will compile.
251+
{
252+
if (!diagnostics.IsEmpty)
253+
Console.WriteLine(result.GeneratedTrees.FirstOrDefault());
212254

213-
diagnostics.ShouldBeEmpty();
255+
diagnostics.ShouldBeEmpty();
256+
}
214257

215258
return result;
216259
}
217260

218-
private static Task Verify([StringSyntax("razor")] string input, [StringSyntax("csharp")] string? csharpCode = null)
261+
private static Task Verify([StringSyntax("razor")] string input,
262+
[StringSyntax("csharp")] string? csharpCode = null,
263+
[StringSyntax("csharp")] string? embeddedLibrary = null)
219264
{
220-
var result = Generate(input, csharpCode);
265+
var result = Generate(input, csharpCode, embeddedLibrary);
221266
return Verifier.Verify(result);
222267
}
223268
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//HintName: TestNamespace.TestFile.Razor.g.cs
2+
#pragma checksum "./TestFile.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "0f387a306a025bdecaddfb34c3e73317fb71c2ac"
3+
// <auto-generated/>
4+
#pragma warning disable 1591
5+
namespace TestNamespace
6+
{
7+
#line hidden
8+
#nullable restore
9+
internal partial class TestFile : Foo.BaseClass
10+
#nullable disable
11+
{
12+
#pragma warning disable 1998
13+
protected async override global::System.Threading.Tasks.Task ExecuteAsync()
14+
{
15+
}
16+
#pragma warning restore 1998
17+
}
18+
}
19+
#pragma warning restore 1591
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//HintName: TestNamespace.TestFile.RazorBlade.g.cs
2+
// <auto-generated/>
3+
4+
#nullable restore
5+
6+
namespace TestNamespace
7+
{
8+
partial class TestFile
9+
{
10+
/// <inheritdoc cref="M:Foo.BaseClass.#ctor(System.Nullable{System.Int32},System.String)" />
11+
public TestFile(int? foo, string? bar)
12+
: base(foo, bar)
13+
{
14+
}
15+
}
16+
}

src/RazorBlade.Analyzers.Tests/Support/AnalyzerConfigOptionsProviderMock.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System;
2-
using System.Collections;
1+
using System.Collections;
32
using System.Collections.Generic;
43
using Microsoft.CodeAnalysis;
54
using Microsoft.CodeAnalysis.Diagnostics;
@@ -28,7 +27,7 @@ public override AnalyzerConfigOptions GetOptions(AdditionalText textFile)
2827
=> GlobalOptions;
2928

3029
public IEnumerator GetEnumerator()
31-
=> throw new NotSupportedException();
30+
=> _values.GetEnumerator();
3231

3332
private class AnalyzerConfigOptionsMock : AnalyzerConfigOptions
3433
{

src/RazorBlade.Analyzers/EmbeddedLibrarySourceGenerator.cs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,12 @@ public class EmbeddedLibrarySourceGenerator : IIncrementalGenerator
1010

1111
public void Initialize(IncrementalGeneratorInitializationContext context)
1212
{
13-
var embedLibrary = context.AnalyzerConfigOptionsProvider
14-
.Select(
15-
static (i, _) => i.GlobalOptions.TryGetValue("build_property.RazorBladeEmbeddedLibrary", out var embedLibraryStr)
16-
&& bool.TryParse(embedLibraryStr, out var embedLibrary)
17-
&& embedLibrary
18-
);
13+
var embeddedLibrary = EmbeddedLibraryFlagProvider(context);
1914

2015
var langVersion = context.ParseOptionsProvider
2116
.Select((parseOptions, _) => ((CSharpParseOptions)parseOptions).LanguageVersion);
2217

23-
var input = embedLibrary.Combine(langVersion);
18+
var input = embeddedLibrary.Combine(langVersion);
2419

2520
context.RegisterSourceOutput(input, static (context, input) =>
2621
{
@@ -35,7 +30,16 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
3530
return;
3631
}
3732

38-
EmbeddedLibrary.AddSource(context);
33+
foreach (var file in EmbeddedLibrary.Files)
34+
context.AddSource($"{file.Name}.g.cs", file.Source);
3935
});
4036
}
37+
38+
public static IncrementalValueProvider<bool> EmbeddedLibraryFlagProvider(IncrementalGeneratorInitializationContext context)
39+
=> context.AnalyzerConfigOptionsProvider
40+
.Select(
41+
static (i, _) => i.GlobalOptions.TryGetValue("build_property.RazorBladeEmbeddedLibrary", out var embedLibraryStr)
42+
&& bool.TryParse(embedLibraryStr, out var embedLibrary)
43+
&& embedLibrary
44+
);
4145
}

src/RazorBlade.Analyzers/LibraryCodeGenerator.cs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,25 @@ private static readonly SymbolDisplayFormat _paramFootprintFormat
4545
private readonly RazorCSharpDocument _generatedDoc;
4646
private readonly Compilation _inputCompilation;
4747
private readonly CSharpParseOptions _parseOptions;
48+
private readonly bool _embeddedLibrary;
4849
private readonly CodeWriter _writer;
4950
private bool _hasCode;
5051

5152
private INamedTypeSymbol? _classSymbol;
5253
private ImmutableArray<Diagnostic> _diagnostics;
54+
private Compilation _compilation;
5355

54-
public LibraryCodeGenerator(RazorCSharpDocument generatedDoc, Compilation compilation, CSharpParseOptions parseOptions)
56+
public LibraryCodeGenerator(RazorCSharpDocument generatedDoc,
57+
Compilation compilation,
58+
CSharpParseOptions parseOptions,
59+
bool embeddedLibrary)
5560
{
5661
_generatedDoc = generatedDoc;
5762
_inputCompilation = compilation;
5863
_parseOptions = parseOptions;
64+
_embeddedLibrary = embeddedLibrary;
5965

66+
_compilation = _inputCompilation;
6067
_writer = new CodeWriter(Environment.NewLine, generatedDoc.Options);
6168
}
6269

@@ -96,10 +103,29 @@ private void Analyze(CancellationToken cancellationToken)
96103
cancellationToken: cancellationToken
97104
);
98105

99-
var compilation = _inputCompilation.WithOptions(_inputCompilation.Options.WithReportSuppressedDiagnostics(true))
100-
.AddSyntaxTrees(syntaxTree);
106+
var additionalSyntaxTrees = new List<SyntaxTree>
107+
{
108+
syntaxTree
109+
};
110+
111+
if (_embeddedLibrary)
112+
{
113+
foreach (var file in EmbeddedLibrary.Files)
114+
{
115+
additionalSyntaxTrees.Add(
116+
CSharpSyntaxTree.ParseText(
117+
file.Source,
118+
_parseOptions,
119+
cancellationToken: cancellationToken
120+
)
121+
);
122+
}
123+
}
124+
125+
_compilation = _inputCompilation.WithOptions(_inputCompilation.Options.WithReportSuppressedDiagnostics(true))
126+
.AddSyntaxTrees(additionalSyntaxTrees);
101127

102-
var semanticModel = compilation.GetSemanticModel(syntaxTree);
128+
var semanticModel = _compilation.GetSemanticModel(syntaxTree);
103129

104130
var classDeclarationNode = syntaxTree.GetRoot(cancellationToken)
105131
.DescendantNodes()
@@ -117,7 +143,7 @@ private void GenerateConstructors()
117143
if (_classSymbol?.BaseType is not { } baseType)
118144
return;
119145

120-
var templateCtorAttribute = _inputCompilation.GetTypeByMetadataName("RazorBlade.Support.TemplateConstructorAttribute");
146+
var templateCtorAttribute = _compilation.GetTypeByMetadataName("RazorBlade.Support.TemplateConstructorAttribute");
121147
if (templateCtorAttribute is null)
122148
return;
123149

@@ -150,7 +176,7 @@ private void GenerateConstructors()
150176

151177
private void GenerateConditionalOnAsync()
152178
{
153-
var conditionalOnAsyncAttribute = _inputCompilation.GetTypeByMetadataName("RazorBlade.Support.ConditionalOnAsyncAttribute");
179+
var conditionalOnAsyncAttribute = _compilation.GetTypeByMetadataName("RazorBlade.Support.ConditionalOnAsyncAttribute");
154180
if (conditionalOnAsyncAttribute is null)
155181
return;
156182

src/RazorBlade.Analyzers/RazorBladeSourceGenerator.cs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,16 @@ public partial class RazorBladeSourceGenerator : IIncrementalGenerator
2020
public void Initialize(IncrementalGeneratorInitializationContext context)
2121
{
2222
var globalOptions = context.ParseOptionsProvider
23-
.Select(static (parseOptions, _) => GetGlobalOptions(parseOptions));
23+
.Combine(EmbeddedLibrarySourceGenerator.EmbeddedLibraryFlagProvider(context))
24+
.Select(static (pair, _) =>
25+
{
26+
var (parseOptions, embeddedLibrary) = pair;
27+
28+
return new GlobalOptions(
29+
(CSharpParseOptions)parseOptions,
30+
embeddedLibrary
31+
);
32+
});
2433

2534
var inputFiles = context.AdditionalTextsProvider
2635
.Where(static i => i.Path.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase))
@@ -48,13 +57,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
4857
);
4958
}
5059

51-
private static GlobalOptions GetGlobalOptions(ParseOptions parseOptions)
52-
{
53-
return new GlobalOptions(
54-
(CSharpParseOptions)parseOptions
55-
);
56-
}
57-
5860
private static InputFile? GetInputFile(AdditionalText additionalText, AnalyzerConfigOptionsProvider optionsProvider)
5961
{
6062
var options = optionsProvider.GetOptions(additionalText);
@@ -156,13 +158,19 @@ private static RazorCSharpDocument GenerateRazorCode(SourceText sourceText, Inpu
156158

157159
private static string GenerateLibrarySpecificCode(RazorCSharpDocument generatedDoc, GlobalOptions globalOptions, Compilation compilation, CancellationToken cancellationToken)
158160
{
159-
var generator = new LibraryCodeGenerator(generatedDoc, compilation, globalOptions.ParseOptions);
161+
var generator = new LibraryCodeGenerator(
162+
generatedDoc,
163+
compilation,
164+
globalOptions.ParseOptions,
165+
globalOptions.EmbeddedLibrary
166+
);
167+
160168
return generator.Generate(cancellationToken);
161169
}
162170

163171
static partial void OnGenerate();
164172

165173
private record InputFile(AdditionalText AdditionalText, string? Namespace, string ClassName);
166174

167-
private record GlobalOptions(CSharpParseOptions ParseOptions);
175+
private record GlobalOptions(CSharpParseOptions ParseOptions, bool EmbeddedLibrary);
168176
}

src/RazorBlade.IntegrationTest.Embedded/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ public static class Program
66
{
77
public static void Main()
88
{
9-
var template = new TestTemplate { Name = "World" };
9+
var template = new TestTemplate("Friends") { Name = "World" };
1010
var result = template.Render();
1111

1212
Console.WriteLine(result);

src/RazorBlade.IntegrationTest.Embedded/TestTemplate.cshtml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
@inherits RazorBlade.HtmlTemplate
1+
@inherits RazorBlade.HtmlTemplate<string>
22

3-
<b>Hello, @Name!</b>
3+
<b>Hello, @Name and @Model!</b>
44

55
@functions {
66
public string? Name { get; set; }

0 commit comments

Comments
 (0)