-
Notifications
You must be signed in to change notification settings - Fork 224
Conversation
var directiveNode = (DirectiveIRNode)Builder.Pop(); | ||
|
||
var tokens = directiveNode.Children.OfType<DirectiveTokenIRNode>().ToList(); | ||
var exceptTokens = directiveNode.Children.Except(tokens).ToList(); |
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 is pretty crappy but for the IR passes to come that will light up directives it'll be important to have easy access to the Tokens.
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 get why you're doing this, but wouldn't it be better to just leave the Children
as is and make Tokens
do something like:
public IEnumerable<DirectiveTokenIRNode> Tokens => Children.OfType<DirectiveTokenIRNode>();
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.
Hmm, sure we can do that 😄
@@ -39,11 +39,16 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer, CSharpS | |||
private Dictionary<CSharpKeyword, Action<bool>> _keywordParsers = new Dictionary<CSharpKeyword, Action<bool>>(); | |||
|
|||
public CSharpCodeParser(ParserContext context) | |||
: this (directiveDescriptors: Enumerable.Empty<DirectiveDescriptor>(), context: context) |
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 talk about how we want to replace existing directives with the directive descriptors. Aka:
- Added in the CSharpCodeParser as "additional" directives?
- Added in the person who constructs CSharpCodeParser in a manner that enables users to remove default directives from the system.
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 think there's any value in making the "built in" directives unparsable by removing them. Users have the ability to make "built in" directives invalid in the IR if they don't want them to be supported.
{ | ||
MapDirectives(() => HandleDirective(directiveDescriptor), directiveDescriptor.Name); | ||
} | ||
|
||
MapDirectives(TagHelperPrefixDirective, SyntaxConstants.CSharp.TagHelperPrefixKeyword); |
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.
These will be able to go away once they're implemented ontop of the directive descriptors. Issue..
|
||
IDirectiveDescriptorBuilder AddString(); | ||
|
||
IDirectiveDescriptorBuilder AddLiteral(string literal, bool optional); |
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.
Don't love the "optional" part of this but this comes into play when doing @addTagHelper "..."
where the quotes are optional. Also works for ending optional semicolons which exist on many of our directives.
If you can think of something better I'm all ears 😄
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.
My two ideas
- Semicolons are always optional in directives (you don't have a choice)
- Don't build
@addTagHelper
on top of extensible directives unless we want the "optional" part to become popular
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.
The design of this looks right on the whole! Bring on the tests.
var directiveNode = (DirectiveIRNode)Builder.Pop(); | ||
|
||
var tokens = directiveNode.Children.OfType<DirectiveTokenIRNode>().ToList(); | ||
var exceptTokens = directiveNode.Children.Except(tokens).ToList(); |
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 get why you're doing this, but wouldn't it be better to just leave the Children
as is and make Tokens
do something like:
public IEnumerable<DirectiveTokenIRNode> Tokens => Children.OfType<DirectiveTokenIRNode>();
|
||
namespace Microsoft.AspNetCore.Razor.Evolution | ||
{ | ||
public class DirectiveDescriptor |
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.
Add a private constructor
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.
That'd break deserialization
|
||
public DirectiveDescriptorType Type { get; set; } | ||
|
||
public IList<DirectiveTokenDescriptor> Tokens { get; 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.
Wait, what? Is this an implementation of the builder or a factory of the builder?
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.
Then how about putting these factory methods on another type - static class DirectiveDescriptorBuilder
|
||
namespace Microsoft.AspNetCore.Razor.Evolution | ||
{ | ||
public enum DirectiveDescriptorType |
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.
IMO we should suffix enums like this with Kind
instead of Type
. It's easy to overload the meaning of type.
|
||
IDirectiveDescriptorBuilder AddString(); | ||
|
||
IDirectiveDescriptorBuilder AddLiteral(string literal, bool optional); |
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.
My two ideas
- Semicolons are always optional in directives (you don't have a choice)
- Don't build
@addTagHelper
on top of extensible directives unless we want the "optional" part to become popular
@@ -39,11 +39,16 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer, CSharpS | |||
private Dictionary<CSharpKeyword, Action<bool>> _keywordParsers = new Dictionary<CSharpKeyword, Action<bool>>(); | |||
|
|||
public CSharpCodeParser(ParserContext context) | |||
: this (directiveDescriptors: Enumerable.Empty<DirectiveDescriptor>(), context: context) |
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 think there's any value in making the "built in" directives unparsable by removing them. Users have the ability to make "built in" directives invalid in the IR if they don't want them to be supported.
30388a5
to
b513709
Compare
🆙 📅 added tests |
749866d
to
cc0ae6a
Compare
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.
} | ||
|
||
return descriptorX != null && | ||
string.Equals(descriptorX.Value, descriptorY.Value, StringComparison.Ordinal) && |
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 can nullref if x != null && y == null
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.
Nope. The if above prevents that 😄
if (descriptorX == descriptorY)
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.
The previous if
is a short-circuit for the case where descriptorX
and descriptorY
are references to the same instance or both null
. Need something like the following before this expression:
if (descriptorX == null || descriptorY == null)
{
// Not reachable if both descriptors are null.
return false;
}
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.
If descriptorX != null then descriptorY != null because the == statement above would have short-circuited.
What you're suggesting is that we return false
instead of true
if both the descriptors being compared are null
. Probably smart. We should do this for our other comparers because they fall into the same trap.
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.
Huh?
If descriptorX != descriptorY
and descriptorX != null
, descriptorY
could be anything except the same reference as descriptoryX
-- including null
. That is, the code currently excludes only a single non-null
value for descriptorY
. The code you checked in can easily null ref.
Second, two null
references should compare equal. I was suggesting an addition:
if (descriptorX == descriptorY)
{
return true;
}
if (descriptorX == null || descriptorY == null)
{
return false;
}
return (string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.Ordinal) &&
descriptorX.Kind == descriptorY.Kind &&
Enumerable.SequenceEqual( ... );
I had a look at the other comparers in Razor. Primarily the test ones are null
-safe; they often Assert.NotNull()
for both parameters.
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.
What he said
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.
And by that I mean, that I agree with my countryman 🇨🇦 🏒 🍁 🇨🇦
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 be dumb 👍
- Based generic directive implementation off of descriptors. - Added parsing logic to consume descriptors and parse content that's expected. - Added parsing errors to automagically detect unexpected directive pieces. - Updated visitor implementations to understand the directive bits. - Added a builder abstraction to easily create descriptors. Had to maintain the ability to manually construct a descriptor to enable convenient serialization/deserialization. - Added tests/comparers to verify correctness of parsing. #853
cc0ae6a
to
518378f
Compare
#853
Will add tests once design looks good.
/cc @rynowak