Skip to content

Commit fa4e463

Browse files
Add ParserOptions and CodeGenerationOptions properties to RazorCodeDocument and rationalize options configuration (#11526)
> [!IMPORTANT] > This contains RazorSDK breaking changes! I've prepared a fix for these changes that can applied when the SDK build fails: DustinCampbell/sdk@4e955e1 > [!WARNING] > This is a big change with a lot of commits. To facilitate code reviews, I've tried to keep the commit history informative. Most commits have explanatory messages and product and pure test changes are in separate commits. The majority of the changes are in the compiler, so this will require two compiler reviews from @chsienki, @333fred, @jjonescz, or @jaredpar. CI Build: https://dev.azure.com/dnceng/internal/_build/results?buildId=2647163&view=results Test Insertion: https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/612761 ## Summary - Refactor and simplify `RazorParserOptions` API and its builder pattern. - Refactor and simplify `RazorCodeGenerationOptions` API and its builder pattern. - Introduce `RazorProjectEngineBuilder` extension methods to configure parser and code generation options. - Consolidate or remove most `IConfigureRazorParserOptionsFeature` and `IConfigureRazorCodeGenerationOptionsFeature` implementations. - Clean up directive configuration to avoid using `ICollection<T>` and `IReadOnlyDictionary<TKey, TValue>`. - Add `ParserOptions` and `CodeGenerationOptions` properties to `RazorCodeDocument` (and remove them from `RazorCodeDocument.Items`) - Ensure that `RazorCodeDocument` is always created with valid parser and code generation options. - Remove the legacy `IRazorParserOptionsFeature` and `IRazorCodeGenerationOptionsFeature` interfaces and their implementations. These served as fallbacks when a `RazorCodeDocument` didn't have parser or code gen options. However, that's no longer the case, so the fallbacks can be removed. - Remove the `IRazorParserOptionsFactoryProjectFeature` and `IRazorCodeGenerationOptionsFactoryProjectFeature` interfaces and their implementations. These were only used by `RazorProjectEngine` when creating a `RazorCodeDocument` and their functionality has been rolled into `RazorProjectEngine.` - Provide several helper methods in the compiler and test infrastructure to make it easy to create a `RazorCodeDocument` from a `RazorProjectEngine`. - Unify the compiler test infrastructure for `RazorProjectEngine`-based tests. The same test infrastructure is shared across the Language and "Extensions" tests - Introduce a `RazorCodeDocumentProcessor` helper class for executing particular compiler phases and passes with a `RazorCodeDocument`. - Graduate many copied-and-pasted test helpers to shared extension methods. - Update 100s of tests to always create `RazorCodeDocuments` from a `RazorProjectEngine`. This ensures that the options are always configured correctly. - Rationalize the `RazorCodeDocument.Create(...)` API. Realistically, this should rarely be used. It's always best to acquire a `RazorCodeDocument` from a `RazorProjectEngine`. - Remove `#nullable disable` where it's easy to do. 😄 > [!TIP] > If you wish to only review product changes, you can skip the following commits. Note that 1, 2, and 15 are strays, but 3-14 are grouped together in the commit history. > > 1. Remove TestRazorCodeDocument.Create(Source, Imports) test helper (4955122) > 2. Fix DefaultRazorParsingPhaseTest (45e62cb) > 3. RazorProjectEngineTestBase: Remove CreateEngine() (ab97efc) > 4. ModelDirectiveTest: Update to create code document from project engine (5e1cd9d) > 5. ModelDirectiveTest: Fix incorrect test assertions (c6382d8) > 6. Refactor to share more test infrastructure (22ec9bd) > 7. PageDirectiveTest: Move to new test infrastructure (4d5954c) > 8. Clean up Version1_X tests (ff5c526) > 9. Clean up Version2_X tests (601d9be) > 10. Finish cleaning up MS.ANC.Mvc.Razor.Extensions.Test (fa98b37) > 11. Refactor and improve the testing API of RazorProjectEngineTestBase (209fa7e) > 12. Rename IntermediateNode test helpers and unneeded remove null assertions (55a3731) > 13. Remove RazorCodeDocument.Create(...) calls from compiler tests (8f2a0f3) > 14. Remove RazorCodeDocument.Create(...) calls from tooling code (9e4db21) > 15. Clean up and document RazorProjectEngineTestBase (2243678)
2 parents d51413c + 7489b4f commit fa4e463

File tree

169 files changed

+5444
-7715
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

169 files changed

+5444
-7715
lines changed
Lines changed: 58 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,51 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
#nullable disable
5-
64
using Microsoft.AspNetCore.Razor.Language;
75
using Microsoft.AspNetCore.Razor.Language.Intermediate;
86
using Xunit;
97

108
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X;
119

12-
public class InjectDirectiveTest
10+
public class InjectDirectiveTest : RazorProjectEngineTestBase
1311
{
12+
protected override RazorLanguageVersion Version => RazorLanguageVersion.Version_1_1;
13+
14+
protected override void ConfigureProjectEngine(RazorProjectEngineBuilder builder)
15+
{
16+
// Notice we're not registering the InjectDirective.Pass here so we can run it on demand.
17+
builder.AddDirective(InjectDirective.Directive);
18+
builder.AddDirective(ModelDirective.Directive);
19+
20+
builder.Features.Add(new RazorPageDocumentClassifierPass());
21+
builder.Features.Add(new MvcViewDocumentClassifierPass());
22+
}
23+
24+
protected override void ConfigureCodeDocumentProcessor(RazorCodeDocumentProcessor processor)
25+
{
26+
processor.ExecutePhasesThrough<IRazorDocumentClassifierPhase>();
27+
}
28+
1429
[Fact]
1530
public void InjectDirectivePass_Execute_DefinesProperty()
1631
{
1732
// Arrange
18-
var codeDocument = CreateDocument(@"
33+
var codeDocument = ProjectEngine.CreateCodeDocument(@"
1934
@inject PropertyType PropertyName
2035
");
2136

22-
var engine = CreateEngine();
23-
var pass = new InjectDirective.Pass()
24-
{
25-
Engine = engine,
26-
};
27-
28-
var irDocument = CreateIRDocument(engine, codeDocument);
37+
var processor = CreateCodeDocumentProcessor(codeDocument);
2938

3039
// Act
31-
pass.Execute(codeDocument, irDocument);
40+
processor.ExecutePass<InjectDirective.Pass>();
3241

3342
// Assert
34-
var @class = FindClassNode(irDocument);
35-
Assert.NotNull(@class);
36-
Assert.Equal(2, @class.Children.Count);
43+
var documentNode = processor.GetDocumentNode();
44+
var classNode = documentNode.GetClassNode();
3745

38-
var node = Assert.IsType<InjectIntermediateNode>(@class.Children[1]);
46+
Assert.Equal(2, classNode.Children.Count);
47+
48+
var node = Assert.IsType<InjectIntermediateNode>(classNode.Children[1]);
3949
Assert.Equal("PropertyType", node.TypeName);
4050
Assert.Equal("PropertyName", node.MemberName);
4151
}
@@ -44,28 +54,23 @@ @inject PropertyType PropertyName
4454
public void InjectDirectivePass_Execute_DedupesPropertiesByName()
4555
{
4656
// Arrange
47-
var codeDocument = CreateDocument(@"
57+
var codeDocument = ProjectEngine.CreateCodeDocument(@"
4858
@inject PropertyType PropertyName
4959
@inject PropertyType2 PropertyName
5060
");
5161

52-
var engine = CreateEngine();
53-
var pass = new InjectDirective.Pass()
54-
{
55-
Engine = engine,
56-
};
57-
58-
var irDocument = CreateIRDocument(engine, codeDocument);
62+
var processor = CreateCodeDocumentProcessor(codeDocument);
5963

6064
// Act
61-
pass.Execute(codeDocument, irDocument);
65+
processor.ExecutePass<InjectDirective.Pass>();
6266

6367
// Assert
64-
var @class = FindClassNode(irDocument);
65-
Assert.NotNull(@class);
66-
Assert.Equal(2, @class.Children.Count);
68+
var documentNode = processor.GetDocumentNode();
69+
var classNode = documentNode.GetClassNode();
70+
71+
Assert.Equal(2, classNode.Children.Count);
6772

68-
var node = Assert.IsType<InjectIntermediateNode>(@class.Children[1]);
73+
var node = Assert.IsType<InjectIntermediateNode>(classNode.Children[1]);
6974
Assert.Equal("PropertyType2", node.TypeName);
7075
Assert.Equal("PropertyName", node.MemberName);
7176
}
@@ -74,27 +79,22 @@ @inject PropertyType2 PropertyName
7479
public void InjectDirectivePass_Execute_ExpandsTModel_WithDynamic()
7580
{
7681
// Arrange
77-
var codeDocument = CreateDocument(@"
82+
var codeDocument = ProjectEngine.CreateCodeDocument(@"
7883
@inject PropertyType<TModel> PropertyName
7984
");
8085

81-
var engine = CreateEngine();
82-
var pass = new InjectDirective.Pass()
83-
{
84-
Engine = engine,
85-
};
86-
87-
var irDocument = CreateIRDocument(engine, codeDocument);
86+
var processor = CreateCodeDocumentProcessor(codeDocument);
8887

8988
// Act
90-
pass.Execute(codeDocument, irDocument);
89+
processor.ExecutePass<InjectDirective.Pass>();
9190

9291
// Assert
93-
var @class = FindClassNode(irDocument);
94-
Assert.NotNull(@class);
95-
Assert.Equal(2, @class.Children.Count);
92+
var documentNode = processor.GetDocumentNode();
93+
var classNode = documentNode.GetClassNode();
94+
95+
Assert.Equal(2, classNode.Children.Count);
9696

97-
var node = Assert.IsType<InjectIntermediateNode>(@class.Children[1]);
97+
var node = Assert.IsType<InjectIntermediateNode>(classNode.Children[1]);
9898
Assert.Equal("PropertyType<dynamic>", node.TypeName);
9999
Assert.Equal("PropertyName", node.MemberName);
100100
}
@@ -103,28 +103,23 @@ @inject PropertyType<TModel> PropertyName
103103
public void InjectDirectivePass_Execute_ExpandsTModel_WithModelTypeFirst()
104104
{
105105
// Arrange
106-
var codeDocument = CreateDocument(@"
106+
var codeDocument = ProjectEngine.CreateCodeDocument(@"
107107
@model ModelType
108108
@inject PropertyType<TModel> PropertyName
109109
");
110110

111-
var engine = CreateEngine();
112-
var pass = new InjectDirective.Pass()
113-
{
114-
Engine = engine,
115-
};
116-
117-
var irDocument = CreateIRDocument(engine, codeDocument);
111+
var processor = CreateCodeDocumentProcessor(codeDocument);
118112

119113
// Act
120-
pass.Execute(codeDocument, irDocument);
114+
processor.ExecutePass<InjectDirective.Pass>();
121115

122116
// Assert
123-
var @class = FindClassNode(irDocument);
124-
Assert.NotNull(@class);
125-
Assert.Equal(2, @class.Children.Count);
117+
var documentNode = processor.GetDocumentNode();
118+
var classNode = documentNode.GetClassNode();
119+
120+
Assert.Equal(2, classNode.Children.Count);
126121

127-
var node = Assert.IsType<InjectIntermediateNode>(@class.Children[1]);
122+
var node = Assert.IsType<InjectIntermediateNode>(classNode.Children[1]);
128123
Assert.Equal("PropertyType<ModelType>", node.TypeName);
129124
Assert.Equal("PropertyName", node.MemberName);
130125
}
@@ -133,81 +128,24 @@ @inject PropertyType<TModel> PropertyName
133128
public void InjectDirectivePass_Execute_ExpandsTModel_WithModelType()
134129
{
135130
// Arrange
136-
var codeDocument = CreateDocument(@"
131+
var codeDocument = ProjectEngine.CreateCodeDocument(@"
137132
@inject PropertyType<TModel> PropertyName
138133
@model ModelType
139134
");
140135

141-
var engine = CreateEngine();
142-
var pass = new InjectDirective.Pass()
143-
{
144-
Engine = engine,
145-
};
146-
147-
var irDocument = CreateIRDocument(engine, codeDocument);
136+
var processor = CreateCodeDocumentProcessor(codeDocument);
148137

149138
// Act
150-
pass.Execute(codeDocument, irDocument);
139+
processor.ExecutePass<InjectDirective.Pass>();
151140

152141
// Assert
153-
var @class = FindClassNode(irDocument);
154-
Assert.NotNull(@class);
155-
Assert.Equal(2, @class.Children.Count);
142+
var documentNode = processor.GetDocumentNode();
143+
var classNode = documentNode.GetClassNode();
144+
145+
Assert.Equal(2, classNode.Children.Count);
156146

157-
var node = Assert.IsType<InjectIntermediateNode>(@class.Children[1]);
147+
var node = Assert.IsType<InjectIntermediateNode>(classNode.Children[1]);
158148
Assert.Equal("PropertyType<ModelType>", node.TypeName);
159149
Assert.Equal("PropertyName", node.MemberName);
160150
}
161-
162-
private RazorCodeDocument CreateDocument(string content)
163-
{
164-
var source = RazorSourceDocument.Create(content, "test.cshtml");
165-
return RazorCodeDocument.Create(source);
166-
}
167-
168-
private ClassDeclarationIntermediateNode FindClassNode(IntermediateNode node)
169-
{
170-
var visitor = new ClassNodeVisitor();
171-
visitor.Visit(node);
172-
return visitor.Node;
173-
}
174-
175-
private RazorEngine CreateEngine()
176-
{
177-
var configuration = new RazorConfiguration(RazorLanguageVersion.Version_1_1, "test", Extensions: []);
178-
return RazorProjectEngine.Create(configuration, RazorProjectFileSystem.Empty, b =>
179-
{
180-
// Notice we're not registering the InjectDirective.Pass here so we can run it on demand.
181-
b.AddDirective(InjectDirective.Directive);
182-
b.AddDirective(ModelDirective.Directive);
183-
184-
b.Features.Add(new RazorPageDocumentClassifierPass());
185-
b.Features.Add(new MvcViewDocumentClassifierPass());
186-
}).Engine;
187-
}
188-
189-
private DocumentIntermediateNode CreateIRDocument(RazorEngine engine, RazorCodeDocument codeDocument)
190-
{
191-
foreach (var phase in engine.Phases)
192-
{
193-
phase.Execute(codeDocument);
194-
195-
if (phase is IRazorDocumentClassifierPhase)
196-
{
197-
break;
198-
}
199-
}
200-
201-
return codeDocument.GetDocumentIntermediateNode();
202-
}
203-
204-
private class ClassNodeVisitor : IntermediateNodeWalker
205-
{
206-
public ClassDeclarationIntermediateNode Node { get; set; }
207-
208-
public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node)
209-
{
210-
Node = node;
211-
}
212-
}
213151
}

0 commit comments

Comments
 (0)