-
Notifications
You must be signed in to change notification settings - Fork 225
Add file scoped extensible directives. #1457
Conversation
return; | ||
} | ||
|
||
var root = Context.Builder.ActiveBlocks.Last(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ajaybhargavb this is the magic that determines if a directive exists prior to code, html etc.
@@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions | |||
public class ModelDirectiveTest | |||
{ | |||
[Fact] | |||
public void ModelDirective_GetModelType_GetsTypeFromLastWellFormedDirective() | |||
public void ModelDirective_GetModelType_GetsTypeFromFirstWellFormedDirective() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means that @model
existing multiple times in a single document results in the first valid @model
being used. If you have a valid model in an import file that carries over and properly overrides proper @model
directives due to how our ModelDirective
code passes over the IR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what you're saying
@@ -39,6 +39,6 @@ public class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_MultipleMo | |||
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] | |||
public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } | |||
[global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] | |||
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<System.Collections.IEnumerable> Html { get; private set; } | |||
public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<ThisShouldBeGenerated> Html { get; private set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should have been this way to start with.
@@ -1062,57 +1351,53 @@ public void AddTagHelperDirective_EndQuoteRequiresDoubleQuotesAroundValue() | |||
} | |||
|
|||
[Fact] | |||
public void ParseBlock_InheritsDirective() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer valid. Have other tests.
I like the naming I think we have the following categories: File Scoped - Singly Occurring I defined Additionally they should have the following semantics: File Scoped - Single Imported directives can be overridden - from the furthest to the closest. An overridden directive does not appear in the IR. It is invisible to user code. If you don't want this, don't do this. File Scoped - Multiple will allow you to see multiple uses of the directive. Using a File Scoped - Single directive multiple times in any single file is an error. Using a File Scoped - Single directive outside of the 'top' of the file is an error. Does not support blocks. Examples: model, inherits, page MultipleFileScoped Imported directives are all added to the IR document - from the furthest to the closest. Using a File Scoped - Multiple directive outside of the 'top' of the file is an error. Does not support blocks. Examples: inject, using Block Imported directives are all added to the IR document - from the furthest to the closest. Can appear anywhere in the document. Examples: section |
For what I'm calling a Block directive, I'm not sure if we should make it possible to make a directive importable/not-importable. |
My guess btw is that the code for this is mostly right, I feel like my concept of this is about 10% off from yours |
Also - notice how 'Block' is pretty much how directives behave today. The File Scoped variations just remove the need to extensiblity authors to do error checking. |
builder => | ||
{ | ||
builder.AddTypeToken(); | ||
builder.Usage = DirectiveUsage.SinglePreContent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should usage just be a required argument to CreateDirective
? I don't have a major gripe with this, I just want to understand how you see the tradeoffs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thought about that approach but didn't feel like it deserved its own argument since having an unrestricted directive was consistent with what we've always had in the past. Aka, users previous expectations of directives is the default experience.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK cool, I think that's fine.
@@ -31,6 +31,11 @@ public interface IDirectiveDescriptorBuilder | |||
DirectiveKind Kind { get; } | |||
|
|||
/// <summary> | |||
/// Gets the directive usage. The usage determines how many, and where directives can exist per document. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gets/sets
@@ -514,6 +514,34 @@ internal static string IntermediateNodeReference_CollectionIsReadOnly | |||
internal static string FormatIntermediateNodeReference_CollectionIsReadOnly(object p0) | |||
=> string.Format(CultureInfo.CurrentCulture, GetString("IntermediateNodeReference_CollectionIsReadOnly"), p0); | |||
|
|||
/// <summary> | |||
/// The '{0}' directive may only occurr once per document. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
occurr
@@ -31,7 +31,7 @@ @model Type2 | |||
var result = ModelDirective.GetModelType(irDocument); | |||
|
|||
// Assert | |||
Assert.Equal("Type2", result); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is fine.
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(14,11): Error RZ9999: The 'namespace' directive expects a namespace name. | ||
TestFiles/IntegrationTests/CodeGenerationIntegrationTest/IncompleteDirectives.cshtml(15,1): Error RZ9999: The 'namespace' directive may only occurr once per document. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
occurrrrrrr
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to have an effect on how imports work. let's discuss tomorrow if you don't like my ideas 😆
74df4a7
to
a6117b6
Compare
Running an MVC build with these bits to ensure I didn't break anything and then will get this in (assuming all is well). I'll then do a follow up PR to implement _ViewImports support by default for |
- Added `DirectiveUsage` to enable extensible directive authors to indicate how their directives should be used. Currently support `Unrestricted` (how section directives have always worked) and a file scoped singly occurring directive. - Added directive parsing tests. - Removed no longer used `BlockKindInternal` items. #1376
- Updated integration code gen and IR bits to reflect new directive usage. - Updated existing unit tests that happened to test directives in code blocks to now test what happens when they exist at the document level. Being inside of a code block is now invalid and we have separate tests for that scenario. #1376
a6117b6
to
1dd4feb
Compare
What's in the PR
Introduced a new
DirectiveUsage
enum
that enables directive authors to specify if they want their directives be have a specific usage. The default (what exists today) isDirectiveUsage.Unrestricted
. The new one that was introduced in this PR (file scoped) is labeled asSinglePreContent
. It's a semi-verbose name but I think it's important that when a user sees the enum they have a clear indication as to what it entails; this of course is further elaborated in doc comments.For a
SinglePreContent
directive there is the following restrictions:With these restrictions in mind these are the high level pieces that this enables
Inheriting Directives
This PR does not contain the code to automatically inherit directives that are labeled as
SinglePreContent
. I thought about making this automatic but felt that it could go in a follow up if deemed important. The primary reason for it not being in this PR is due to it requiring a change in design on how we inherit directives. Today we append imported IR to the bottom of our IR document; this isn't sufficient if you have a default importing story. If we start specifying how directives are inherited we then need to start markingDirectiveIntermediateNode
s (in a way that stops overridden directives from flowing into extensibility) as inherited or not inherited and enable ways on how to change that.DirectiveUsage
to enable extensible directive authors to indicate how their directives should be used. Currently supportUnrestricted
(how section directives have always worked and a single pre-content based directive.BlockKindInternal
items.#1376
/cc @rynowak @ajaybhargavb