Skip to content
This repository has been archived by the owner on Dec 19, 2018. It is now read-only.

Add file scoped extensible directives. #1457

Merged
merged 2 commits into from
Jun 22, 2017
Merged

Add file scoped extensible directives. #1457

merged 2 commits into from
Jun 22, 2017

Conversation

NTaylorMullen
Copy link
Member

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) is DirectiveUsage.Unrestricted. The new one that was introduced in this PR (file scoped) is labeled as SinglePreContent. 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:

  1. Must exist prior to any statements or expressions
  2. Must exist prior to any HTML (including TagHelpers)
  3. Must not have any duplicates

With these restrictions in mind these are the high level pieces that this enables

  • Directives sitting next to other directives
  • Directives sitting indiscriminately next to any number of whitespace
  • Directives being decorated with comments

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 marking DirectiveIntermediateNodes (in a way that stops overridden directives from flowing into extensibility) as inherited or not inherited and enable ways on how to change that.


  • 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 single pre-content based directive.
  • Added directive parsing tests.
  • Removed no longer used BlockKindInternal items.
  • Update built-in directives to use DirectiveUsage.
  • 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

/cc @rynowak @ajaybhargavb

return;
}

var root = Context.Builder.ActiveBlocks.Last();
Copy link
Member Author

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()
Copy link
Member Author

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.

Copy link
Member

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; }
Copy link
Member Author

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()
Copy link
Member Author

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.

@rynowak
Copy link
Member

rynowak commented Jun 22, 2017

I like the naming File Scoped rather than pre-content. I call it file scoped, because the unit of operation is the file, and it is only allowed at the top level of scoping.

I think we have the following categories:

File Scoped - Singly Occurring
File Scoped - Multiple Occurring
Block Scoped

I defined File Scoped in the same way that you define PreContent as far as lexically how they are in Razor code. I think that part is perfect in your description.


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

@rynowak
Copy link
Member

rynowak commented Jun 22, 2017

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.

@rynowak
Copy link
Member

rynowak commented Jun 22, 2017

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

@rynowak
Copy link
Member

rynowak commented Jun 22, 2017

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;
Copy link
Member

@rynowak rynowak Jun 22, 2017

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.

Copy link
Member Author

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.

Copy link
Member

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.
Copy link
Member

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.
Copy link
Member

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);
Copy link
Member

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

occurrrrrrr

Copy link
Member

@rynowak rynowak left a 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 😆

@NTaylorMullen
Copy link
Member Author

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 FileScopedSinglyOccurring directive usages.

- 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
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants