Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix attribute parsing recovery #10620

Merged
merged 6 commits into from
Jul 19, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1085,16 +1085,50 @@ private void ParseAttribute(in SyntaxListBuilder<RazorSyntaxNode> builder)
// http://dev.w3.org/html5/spec/tokenization.html#attribute-name-state
// Read the 'name' (i.e. read until the '=' or whitespace/newline)
using var nameTokens = new PooledArrayBuilder<SyntaxToken>();
if (!TryParseAttributeName(ref nameTokens.AsRef()))
if (TryParseAttributeName(out SyntaxToken? ephemeralToken, ref nameTokens.AsRef())
DustinCampbell marked this conversation as resolved.
Show resolved Hide resolved
is not AttributeNameParsingResult.Success and { } nameParsingResult)
{
// Parse C# or razor comment and return to attribute parsing afterwards.
switch (nameParsingResult)
{
case AttributeNameParsingResult.CSharp:
{
Accept(in attributePrefixWhitespace);
PutCurrentBack();
using var pooledResult = Pool.Allocate<RazorSyntaxNode>();
var dynamicAttributeValueBuilder = pooledResult.Builder;
OtherParserBlock(dynamicAttributeValueBuilder);
var value = SyntaxFactory.MarkupMiscAttributeContent(dynamicAttributeValueBuilder.ToList());
builder.Add(value);
return;
}
case AttributeNameParsingResult.RazorComment:
{
Accept(in attributePrefixWhitespace);
PutCurrentBack();
ParseRazorCommentWithLeadingAndTrailingWhitespace(builder);
return;
}
}

// Unexpected character in tag, enter recovery
Debug.Assert(nameParsingResult is AttributeNameParsingResult.Other);
Accept(in attributePrefixWhitespace);
ParseMiscAttribute(builder);
return;
}

Accept(in attributePrefixWhitespace); // Whitespace before attribute name
var namePrefix = OutputAsMarkupLiteral();

if (ephemeralToken is not null)
{
builder.Add(namePrefix);
Accept(ephemeralToken);
builder.Add(OutputAsMarkupEphemeralLiteral());
namePrefix = null;
}

Accept(in nameTokens); // Attribute name
var name = OutputAsMarkupLiteralRequired();

Expand All @@ -1113,18 +1147,44 @@ private void ParseAttribute(in SyntaxListBuilder<RazorSyntaxNode> builder)
}
}

private bool TryParseAttributeName(ref PooledArrayBuilder<SyntaxToken> nameTokens)
private enum AttributeNameParsingResult
{
Success,
Other,
CSharp,
RazorComment,
}

private AttributeNameParsingResult TryParseAttributeName(out SyntaxToken? ephemeralToken, ref PooledArrayBuilder<SyntaxToken> nameTokens)
{
ephemeralToken = null;

//
// We are currently here <input |name="..." />
// If we encounter a transition (@) here, it can be parsed as CSharp or Markup depending on the feature flag.
// For example, in Components, we want to parse it as Markup so we can support directive attributes.
//
if (Context.FeatureFlags.AllowCSharpInMarkupAttributeArea &&
(At(SyntaxKind.Transition) || At(SyntaxKind.RazorCommentTransition)))
if (Context.FeatureFlags.AllowCSharpInMarkupAttributeArea)
{
// If we get here, there is CSharp in the attribute area. Don't try to parse the name.
return false;
if (At(SyntaxKind.Transition))
{
if (NextIs(SyntaxKind.Transition))
{
// The attribute name is escaped (@@), eat the first @ sign.
ephemeralToken = CurrentToken;
NextToken();
}
else
{
// There is CSharp in the attribute area. Don't try to parse the name.
return AttributeNameParsingResult.CSharp;
}
}
else if (At(SyntaxKind.RazorCommentTransition))
{
// There is razor comment in the attribute area. Don't try to parse the name.
return AttributeNameParsingResult.RazorComment;
}
}

if (IsValidAttributeNameToken(CurrentToken))
Expand All @@ -1140,10 +1200,10 @@ private bool TryParseAttributeName(ref PooledArrayBuilder<SyntaxToken> nameToken
this,
ref nameTokens);

return true;
return AttributeNameParsingResult.Success;
}

return false;
return AttributeNameParsingResult.Other;
}

private MarkupAttributeBlockSyntax ParseRemainingAttribute(MarkupTextLiteralSyntax? namePrefix, MarkupTextLiteralSyntax name)
Expand Down