Added RazorParserFeatureFlags and added support for minimized bool tag
helper bound attributes - Fixes #1678, #431
This commit is contained in:
parent
edcf6857d1
commit
bd8e9ecc31
|
|
@ -21,10 +21,14 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
|
||||
public bool IsIndexerStringProperty { get; protected set; }
|
||||
|
||||
public bool IsIndexerBooleanProperty { get; protected set; }
|
||||
|
||||
public bool IsEnum { get; protected set; }
|
||||
|
||||
public bool IsStringProperty { get; protected set; }
|
||||
|
||||
public bool IsBooleanProperty { get; protected set; }
|
||||
|
||||
public string Name { get; protected set; }
|
||||
|
||||
public string IndexerNamePrefix { get; protected set; }
|
||||
|
|
|
|||
|
|
@ -27,5 +27,27 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
|
||||
return string.Equals(attribute.Kind, TagHelperConventions.DefaultKind, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
internal static bool ExpectsStringValue(this BoundAttributeDescriptor attribute, string name)
|
||||
{
|
||||
if (attribute.IsStringProperty)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var isIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(name, attribute);
|
||||
return isIndexerNameMatch && attribute.IsIndexerStringProperty;
|
||||
}
|
||||
|
||||
internal static bool ExpectsBooleanValue(this BoundAttributeDescriptor attribute, string name)
|
||||
{
|
||||
if (attribute.IsBooleanProperty)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var isIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(name, attribute);
|
||||
return isIndexerNameMatch && attribute.IsIndexerBooleanProperty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -35,6 +35,9 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
|
||||
IsIndexerStringProperty = indexerTypeName == typeof(string).FullName || indexerTypeName == "string";
|
||||
IsStringProperty = typeName == typeof(string).FullName || typeName == "string";
|
||||
|
||||
IsIndexerBooleanProperty = indexerTypeName == typeof(bool).FullName || indexerTypeName == "bool";
|
||||
IsBooleanProperty = typeName == typeof(bool).FullName || typeName == "bool";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
var imports = codeDocument.GetImportSyntaxTrees();
|
||||
if (imports != null)
|
||||
{
|
||||
var importsVisitor = new ImportsVisitor(document, builder, namespaces);
|
||||
var importsVisitor = new ImportsVisitor(document, builder, namespaces, syntaxTree.Options.FeatureFlags);
|
||||
|
||||
for (var j = 0; j < imports.Count; j++)
|
||||
{
|
||||
|
|
@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
}
|
||||
|
||||
var tagHelperPrefix = tagHelperContext?.Prefix;
|
||||
var visitor = new MainSourceVisitor(document, builder, namespaces, tagHelperPrefix)
|
||||
var visitor = new MainSourceVisitor(document, builder, namespaces, tagHelperPrefix, syntaxTree.Options.FeatureFlags)
|
||||
{
|
||||
FilePath = syntaxTree.Source.FilePath,
|
||||
};
|
||||
|
|
@ -151,12 +151,14 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
protected readonly IntermediateNodeBuilder _builder;
|
||||
protected readonly DocumentIntermediateNode _document;
|
||||
protected readonly Dictionary<string, SourceSpan?> _namespaces;
|
||||
protected readonly RazorParserFeatureFlags _featureFlags;
|
||||
|
||||
public LoweringVisitor(DocumentIntermediateNode document, IntermediateNodeBuilder builder, Dictionary<string, SourceSpan?> namespaces)
|
||||
public LoweringVisitor(DocumentIntermediateNode document, IntermediateNodeBuilder builder, Dictionary<string, SourceSpan?> namespaces, RazorParserFeatureFlags featureFlags)
|
||||
{
|
||||
_document = document;
|
||||
_builder = builder;
|
||||
_namespaces = namespaces;
|
||||
_featureFlags = featureFlags;
|
||||
}
|
||||
|
||||
public string FilePath { get; set; }
|
||||
|
|
@ -331,8 +333,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
|
||||
protected SourceSpan? BuildSourceSpanFromNode(SyntaxTreeNode node)
|
||||
{
|
||||
var location = node.Start;
|
||||
if (location == SourceLocation.Undefined)
|
||||
if (node == null || node.Start == SourceLocation.Undefined)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
|
@ -351,8 +352,8 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
{
|
||||
private readonly string _tagHelperPrefix;
|
||||
|
||||
public MainSourceVisitor(DocumentIntermediateNode document, IntermediateNodeBuilder builder, Dictionary<string, SourceSpan?> namespaces, string tagHelperPrefix)
|
||||
: base(document, builder, namespaces)
|
||||
public MainSourceVisitor(DocumentIntermediateNode document, IntermediateNodeBuilder builder, Dictionary<string, SourceSpan?> namespaces, string tagHelperPrefix, RazorParserFeatureFlags featureFlags)
|
||||
: base(document, builder, namespaces, featureFlags)
|
||||
{
|
||||
_tagHelperPrefix = tagHelperPrefix;
|
||||
}
|
||||
|
|
@ -670,10 +671,11 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
|
||||
if (associatedDescriptors.Any() && renderedBoundAttributeNames.Add(attribute.Name))
|
||||
{
|
||||
if (attributeValueNode == null)
|
||||
var isMinimizedAttribute = attributeValueNode == null;
|
||||
if (isMinimizedAttribute && !_featureFlags.AllowMinimizedBooleanTagHelperAttributes)
|
||||
{
|
||||
// Minimized attributes are not valid for bound attributes. TagHelperBlockRewriter has already
|
||||
// logged an error if it was a bound attribute; so we can skip.
|
||||
// Minimized attributes are not valid for non-boolean bound attributes. TagHelperBlockRewriter
|
||||
// has already logged an error if it was a non-boolean bound attribute; so we can skip.
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -684,6 +686,14 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
return TagHelperMatchingConventions.CanSatisfyBoundAttribute(attribute.Name, a);
|
||||
});
|
||||
|
||||
var expectsBooleanValue = associatedAttributeDescriptor.ExpectsBooleanValue(attribute.Name);
|
||||
|
||||
if (isMinimizedAttribute && !expectsBooleanValue)
|
||||
{
|
||||
// We do not allow minimized non-boolean bound attributes.
|
||||
continue;
|
||||
}
|
||||
|
||||
var setTagHelperProperty = new TagHelperPropertyIntermediateNode()
|
||||
{
|
||||
AttributeName = attribute.Name,
|
||||
|
|
@ -695,7 +705,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
};
|
||||
|
||||
_builder.Push(setTagHelperProperty);
|
||||
attributeValueNode.Accept(this);
|
||||
attributeValueNode?.Accept(this);
|
||||
_builder.Pop();
|
||||
}
|
||||
}
|
||||
|
|
@ -720,8 +730,8 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
|
||||
private class ImportsVisitor : LoweringVisitor
|
||||
{
|
||||
public ImportsVisitor(DocumentIntermediateNode document, IntermediateNodeBuilder builder, Dictionary<string, SourceSpan?> namespaces)
|
||||
: base(document, new ImportBuilder(builder), namespaces)
|
||||
public ImportsVisitor(DocumentIntermediateNode document, IntermediateNodeBuilder builder, Dictionary<string, SourceSpan?> namespaces, RazorParserFeatureFlags featureFlags)
|
||||
: base(document, new ImportBuilder(builder), namespaces, featureFlags)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
{
|
||||
internal class DefaultRazorParserOptions : RazorParserOptions
|
||||
{
|
||||
public DefaultRazorParserOptions(DirectiveDescriptor[] directives, bool designTime, bool parseLeadingDirectives)
|
||||
public DefaultRazorParserOptions(DirectiveDescriptor[] directives, bool designTime, bool parseLeadingDirectives, RazorLanguageVersion version)
|
||||
{
|
||||
if (directives == null)
|
||||
{
|
||||
|
|
@ -18,6 +18,8 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
Directives = directives;
|
||||
DesignTime = designTime;
|
||||
ParseLeadingDirectives = parseLeadingDirectives;
|
||||
Version = version;
|
||||
FeatureFlags = RazorParserFeatureFlags.Create(Version);
|
||||
}
|
||||
|
||||
public override bool DesignTime { get; }
|
||||
|
|
@ -25,5 +27,9 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
public override IReadOnlyCollection<DirectiveDescriptor> Directives { get; }
|
||||
|
||||
public override bool ParseLeadingDirectives { get; }
|
||||
|
||||
public override RazorLanguageVersion Version { get; }
|
||||
|
||||
internal override RazorParserFeatureFlags FeatureFlags { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
|
|
@ -8,9 +9,10 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
{
|
||||
internal class DefaultRazorParserOptionsBuilder : RazorParserOptionsBuilder
|
||||
{
|
||||
public DefaultRazorParserOptionsBuilder(bool designTime)
|
||||
public DefaultRazorParserOptionsBuilder(bool designTime, RazorLanguageVersion version)
|
||||
{
|
||||
DesignTime = designTime;
|
||||
Version = version;
|
||||
}
|
||||
|
||||
public override bool DesignTime { get; }
|
||||
|
|
@ -19,9 +21,11 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
|
||||
public override bool ParseLeadingDirectives { get; set; }
|
||||
|
||||
public override RazorLanguageVersion Version { get; }
|
||||
|
||||
public override RazorParserOptions Build()
|
||||
{
|
||||
return new DefaultRazorParserOptions(Directives.ToArray(), DesignTime, ParseLeadingDirectives);
|
||||
return new DefaultRazorParserOptions(Directives.ToArray(), DesignTime, ParseLeadingDirectives, Version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
internal class DefaultRazorParserOptionsFeature : RazorEngineFeatureBase, IRazorParserOptionsFeature
|
||||
{
|
||||
private readonly bool _designTime;
|
||||
private readonly RazorLanguageVersion _version;
|
||||
private IConfigureRazorParserOptionsFeature[] _configureOptions;
|
||||
|
||||
public DefaultRazorParserOptionsFeature(bool designTime)
|
||||
public DefaultRazorParserOptionsFeature(bool designTime, RazorLanguageVersion version)
|
||||
{
|
||||
_designTime = designTime;
|
||||
_version = version;
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
|
|
@ -22,7 +24,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
|
||||
public RazorParserOptions GetOptions()
|
||||
{
|
||||
var builder = new DefaultRazorParserOptionsBuilder(_designTime);
|
||||
var builder = new DefaultRazorParserOptionsBuilder(_designTime, _version);
|
||||
for (var i = 0; i < _configureOptions.Length; i++)
|
||||
{
|
||||
_configureOptions[i].Configure(builder);
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
}
|
||||
|
||||
var errorSink = new ErrorSink();
|
||||
var rewriter = new TagHelperParseTreeRewriter(tagHelperPrefix, descriptors);
|
||||
var rewriter = new TagHelperParseTreeRewriter(tagHelperPrefix, descriptors, syntaxTree.Options.FeatureFlags);
|
||||
|
||||
var root = syntaxTree.Root;
|
||||
root = rewriter.Rewrite(root, errorSink);
|
||||
|
||||
|
|
|
|||
|
|
@ -336,7 +336,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
|
|||
}
|
||||
|
||||
// If we get there, this is the first time seeing this property so we need to evaluate the expression.
|
||||
if (node.BoundAttribute.IsStringProperty || (node.IsIndexerNameMatch && node.BoundAttribute.IsIndexerStringProperty))
|
||||
if (node.BoundAttribute.ExpectsStringValue(node.AttributeName))
|
||||
{
|
||||
if (DesignTime)
|
||||
{
|
||||
|
|
@ -407,7 +407,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
|
|||
context.CodeWriter.WriteStartAssignment(GetPropertyAccessor(node));
|
||||
}
|
||||
|
||||
RenderTagHelperAttributeInline(context, node, node.Source);
|
||||
if (node.Children.Count == 0 &&
|
||||
node.AttributeStructure == AttributeStructure.Minimized &&
|
||||
node.BoundAttribute.ExpectsBooleanValue(node.AttributeName))
|
||||
{
|
||||
// If this is a minimized boolean attribute, set the value to true.
|
||||
context.CodeWriter.Write("true");
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderTagHelperAttributeInline(context, node, node.Source);
|
||||
}
|
||||
|
||||
context.CodeWriter.WriteLine(";");
|
||||
}
|
||||
|
|
@ -429,7 +439,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
|
|||
.Write(".");
|
||||
}
|
||||
|
||||
RenderTagHelperAttributeInline(context, node, node.Source);
|
||||
if (node.Children.Count == 0 &&
|
||||
node.AttributeStructure == AttributeStructure.Minimized &&
|
||||
node.BoundAttribute.ExpectsBooleanValue(node.AttributeName))
|
||||
{
|
||||
// If this is a minimized boolean attribute, set the value to true.
|
||||
context.CodeWriter.Write("true");
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderTagHelperAttributeInline(context, node, node.Source);
|
||||
}
|
||||
|
||||
context.CodeWriter.WriteLine(";");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,13 +16,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
public static TagHelperBlockBuilder Rewrite(
|
||||
string tagName,
|
||||
bool validStructure,
|
||||
RazorParserFeatureFlags featureFlags,
|
||||
Block tag,
|
||||
TagHelperBinding bindingResult,
|
||||
ErrorSink errorSink)
|
||||
{
|
||||
// There will always be at least one child for the '<'.
|
||||
var start = tag.Children.First().Start;
|
||||
var attributes = GetTagAttributes(tagName, validStructure, tag, bindingResult, errorSink);
|
||||
var attributes = GetTagAttributes(tagName, validStructure, tag, bindingResult, errorSink, featureFlags);
|
||||
var tagMode = GetTagMode(tagName, tag, bindingResult, errorSink);
|
||||
|
||||
return new TagHelperBlockBuilder(tagName, tagMode, start, attributes, bindingResult);
|
||||
|
|
@ -33,7 +34,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
bool validStructure,
|
||||
Block tagBlock,
|
||||
TagHelperBinding bindingResult,
|
||||
ErrorSink errorSink)
|
||||
ErrorSink errorSink,
|
||||
RazorParserFeatureFlags featureFlags)
|
||||
{
|
||||
var attributes = new List<TagHelperAttributeNode>();
|
||||
|
||||
|
|
@ -61,10 +63,15 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
{
|
||||
SourceLocation? errorLocation = null;
|
||||
|
||||
// Check if it's a bound attribute that is minimized or if it's a bound non-string attribute that
|
||||
// is null or whitespace.
|
||||
if ((result.IsBoundAttribute && result.AttributeValueNode == null) ||
|
||||
(result.IsBoundNonStringAttribute &&
|
||||
// Check if it's a non-boolean bound attribute that is minimized or if it's a bound
|
||||
// non-string attribute that has null or whitespace content.
|
||||
var isMinimized = result.AttributeValueNode == null;
|
||||
var isValidMinimizedAttribute = featureFlags.AllowMinimizedBooleanTagHelperAttributes && result.IsBoundBooleanAttribute;
|
||||
if ((isMinimized &&
|
||||
result.IsBoundAttribute &&
|
||||
!isValidMinimizedAttribute) ||
|
||||
(!isMinimized &&
|
||||
result.IsBoundNonStringAttribute &&
|
||||
IsNullOrWhitespaceAttributeValue(result.AttributeValueNode)))
|
||||
{
|
||||
errorLocation = GetAttributeNameStartLocation(child);
|
||||
|
|
@ -690,9 +697,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
{
|
||||
var firstBoundAttribute = FindFirstBoundAttribute(name, descriptors);
|
||||
var isBoundAttribute = firstBoundAttribute != null;
|
||||
var isBoundNonStringAttribute = isBoundAttribute &&
|
||||
!(firstBoundAttribute.IsStringProperty ||
|
||||
(TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(name, firstBoundAttribute) && firstBoundAttribute.IsIndexerStringProperty));
|
||||
var isBoundNonStringAttribute = isBoundAttribute && !firstBoundAttribute.ExpectsStringValue(name);
|
||||
var isBoundBooleanAttribute = isBoundAttribute && firstBoundAttribute.ExpectsBooleanValue(name);
|
||||
var isMissingDictionaryKey = isBoundAttribute &&
|
||||
firstBoundAttribute.IndexerNamePrefix != null &&
|
||||
name.Length == firstBoundAttribute.IndexerNamePrefix.Length;
|
||||
|
|
@ -709,6 +715,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
AttributeName = name,
|
||||
IsBoundAttribute = isBoundAttribute,
|
||||
IsBoundNonStringAttribute = isBoundNonStringAttribute,
|
||||
IsBoundBooleanAttribute = isBoundBooleanAttribute,
|
||||
IsMissingDictionaryKey = isMissingDictionaryKey,
|
||||
IsDuplicateAttribute = isDuplicateAttribute
|
||||
};
|
||||
|
|
@ -766,6 +773,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
|
||||
public bool IsBoundNonStringAttribute { get; set; }
|
||||
|
||||
public bool IsBoundBooleanAttribute { get; set; }
|
||||
|
||||
public bool IsMissingDictionaryKey { get; set; }
|
||||
|
||||
public bool IsDuplicateAttribute { get; set; }
|
||||
|
|
|
|||
|
|
@ -44,8 +44,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
private readonly Stack<BlockBuilder> _blockStack;
|
||||
private TagHelperBlockTracker _currentTagHelperTracker;
|
||||
private BlockBuilder _currentBlock;
|
||||
private RazorParserFeatureFlags _featureFlags;
|
||||
|
||||
public TagHelperParseTreeRewriter(string tagHelperPrefix, IEnumerable<TagHelperDescriptor> descriptors)
|
||||
public TagHelperParseTreeRewriter(
|
||||
string tagHelperPrefix,
|
||||
IEnumerable<TagHelperDescriptor> descriptors,
|
||||
RazorParserFeatureFlags featureFlags)
|
||||
{
|
||||
_tagHelperPrefix = tagHelperPrefix;
|
||||
_tagHelperBinder = new TagHelperBinder(tagHelperPrefix, descriptors);
|
||||
|
|
@ -53,6 +57,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
_blockStack = new Stack<BlockBuilder>();
|
||||
_attributeValueBuilder = new StringBuilder();
|
||||
_htmlAttributeTracker = new List<KeyValuePair<string, string>>();
|
||||
_featureFlags = featureFlags;
|
||||
}
|
||||
|
||||
private TagBlockTracker CurrentTracker => _trackerStack.Count > 0 ? _trackerStack.Peek() : null;
|
||||
|
|
@ -225,6 +230,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
var builder = TagHelperBlockRewriter.Rewrite(
|
||||
tagName,
|
||||
validTagStructure,
|
||||
_featureFlags,
|
||||
tagBlock,
|
||||
tagHelperBinding,
|
||||
errorSink);
|
||||
|
|
|
|||
|
|
@ -682,6 +682,20 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
internal static string FormatTagHelperPrefixDirective_Description()
|
||||
=> GetString("TagHelperPrefixDirective_Description");
|
||||
|
||||
/// <summary>
|
||||
/// Provided value for razor language version is unsupported or invalid: '{0}'.
|
||||
/// </summary>
|
||||
internal static string InvalidRazorLanguageVersion
|
||||
{
|
||||
get => GetString("InvalidRazorLanguageVersion");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provided value for razor language version is unsupported or invalid: '{0}'.
|
||||
/// </summary>
|
||||
internal static string FormatInvalidRazorLanguageVersion(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidRazorLanguageVersion"), p0);
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
internal static void AddRuntimeDefaults(IRazorEngineBuilder builder)
|
||||
{
|
||||
// Configure options
|
||||
builder.Features.Add(new DefaultRazorParserOptionsFeature(designTime: false));
|
||||
builder.Features.Add(new DefaultRazorParserOptionsFeature(designTime: false, version: RazorParserOptions.LatestRazorLanguageVersion));
|
||||
builder.Features.Add(new DefaultRazorCodeGenerationOptionsFeature(designTime: false));
|
||||
|
||||
// Intermediate Node Passes
|
||||
|
|
@ -116,7 +116,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
internal static void AddDesignTimeDefaults(IRazorEngineBuilder builder)
|
||||
{
|
||||
// Configure options
|
||||
builder.Features.Add(new DefaultRazorParserOptionsFeature(designTime: true));
|
||||
builder.Features.Add(new DefaultRazorParserOptionsFeature(designTime: true, version: RazorParserOptions.LatestRazorLanguageVersion));
|
||||
builder.Features.Add(new DefaultRazorCodeGenerationOptionsFeature(designTime: true));
|
||||
builder.Features.Add(new SuppressChecksumOptionsFeature());
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
public enum RazorLanguageVersion
|
||||
{
|
||||
Version1_0 = 1,
|
||||
|
||||
Version1_1 = 2,
|
||||
|
||||
Version2_0 = 3,
|
||||
|
||||
Version2_1 = 4,
|
||||
}
|
||||
|
||||
internal static class RazorLanguageVersionExtensions
|
||||
{
|
||||
internal static bool IsValid(this RazorLanguageVersion version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case RazorLanguageVersion.Version1_0:
|
||||
case RazorLanguageVersion.Version1_1:
|
||||
case RazorLanguageVersion.Version2_0:
|
||||
case RazorLanguageVersion.Version2_1:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
internal abstract class RazorParserFeatureFlags
|
||||
{
|
||||
public static RazorParserFeatureFlags Create(RazorLanguageVersion version)
|
||||
{
|
||||
if (!version.IsValid())
|
||||
{
|
||||
throw new ArgumentException(Resources.FormatInvalidRazorLanguageVersion(version.ToString()));
|
||||
}
|
||||
|
||||
var allowMinimizedBooleanTagHelperAttributes = false;
|
||||
|
||||
if (version == RazorLanguageVersion.Version2_1)
|
||||
{
|
||||
allowMinimizedBooleanTagHelperAttributes = true;
|
||||
}
|
||||
|
||||
return new DefaultRazorParserFeatureFlags(allowMinimizedBooleanTagHelperAttributes);
|
||||
}
|
||||
|
||||
public abstract bool AllowMinimizedBooleanTagHelperAttributes { get; }
|
||||
|
||||
private class DefaultRazorParserFeatureFlags : RazorParserFeatureFlags
|
||||
{
|
||||
public DefaultRazorParserFeatureFlags(bool allowMinimizedBooleanTagHelperAttributes)
|
||||
{
|
||||
AllowMinimizedBooleanTagHelperAttributes = allowMinimizedBooleanTagHelperAttributes;
|
||||
}
|
||||
|
||||
public override bool AllowMinimizedBooleanTagHelperAttributes { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,9 +8,15 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
{
|
||||
public abstract class RazorParserOptions
|
||||
{
|
||||
internal static readonly RazorLanguageVersion LatestRazorLanguageVersion = RazorLanguageVersion.Version2_1;
|
||||
|
||||
public static RazorParserOptions CreateDefault()
|
||||
{
|
||||
return new DefaultRazorParserOptions(Array.Empty<DirectiveDescriptor>(), designTime: false, parseLeadingDirectives: false);
|
||||
return new DefaultRazorParserOptions(
|
||||
Array.Empty<DirectiveDescriptor>(),
|
||||
designTime: false,
|
||||
parseLeadingDirectives: false,
|
||||
version: LatestRazorLanguageVersion);
|
||||
}
|
||||
|
||||
public static RazorParserOptions Create(Action<RazorParserOptionsBuilder> configure)
|
||||
|
|
@ -20,7 +26,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
throw new ArgumentNullException(nameof(configure));
|
||||
}
|
||||
|
||||
var builder = new DefaultRazorParserOptionsBuilder(designTime: false);
|
||||
var builder = new DefaultRazorParserOptionsBuilder(designTime: false, version: LatestRazorLanguageVersion);
|
||||
configure(builder);
|
||||
var options = builder.Build();
|
||||
|
||||
|
|
@ -34,7 +40,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
throw new ArgumentNullException(nameof(configure));
|
||||
}
|
||||
|
||||
var builder = new DefaultRazorParserOptionsBuilder(designTime: true);
|
||||
var builder = new DefaultRazorParserOptionsBuilder(designTime: true, version: LatestRazorLanguageVersion);
|
||||
configure(builder);
|
||||
var options = builder.Build();
|
||||
|
||||
|
|
@ -54,5 +60,9 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
/// In a future release this may be updated to include all leading directive content.
|
||||
/// </remarks>
|
||||
public abstract bool ParseLeadingDirectives { get; }
|
||||
|
||||
public virtual RazorLanguageVersion Version { get; } = LatestRazorLanguageVersion;
|
||||
|
||||
internal virtual RazorParserFeatureFlags FeatureFlags { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
|
|
@ -13,6 +14,8 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
|
||||
public abstract bool ParseLeadingDirectives { get; set; }
|
||||
|
||||
public virtual RazorLanguageVersion Version { get; }
|
||||
|
||||
public abstract RazorParserOptions Build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -261,4 +261,7 @@
|
|||
<data name="TagHelperPrefixDirective_Description" xml:space="preserve">
|
||||
<value>Specify a prefix that is required in an element name for it to be included in Tag Helper processing.</value>
|
||||
</data>
|
||||
<data name="InvalidRazorLanguageVersion" xml:space="preserve">
|
||||
<value>Provided value for razor language version is unsupported or invalid: '{0}'.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -95,5 +95,185 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
// Assert
|
||||
Assert.False(isDefault);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpectsStringValue_ReturnsTrue_ForStringProperty()
|
||||
{
|
||||
// Arrange
|
||||
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
|
||||
tagHelperBuilder.TypeName("TestTagHelper");
|
||||
|
||||
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
|
||||
builder
|
||||
.Name("test")
|
||||
.PropertyName("BoundProp")
|
||||
.TypeName(typeof(string).FullName);
|
||||
|
||||
var descriptor = builder.Build();
|
||||
|
||||
// Act
|
||||
var result = descriptor.ExpectsStringValue("test");
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpectsStringValue_ReturnsFalse_ForNonStringProperty()
|
||||
{
|
||||
// Arrange
|
||||
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
|
||||
tagHelperBuilder.TypeName("TestTagHelper");
|
||||
|
||||
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
|
||||
builder
|
||||
.Name("test")
|
||||
.PropertyName("BoundProp")
|
||||
.TypeName(typeof(bool).FullName);
|
||||
|
||||
var descriptor = builder.Build();
|
||||
|
||||
// Act
|
||||
var result = descriptor.ExpectsStringValue("test");
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpectsStringValue_ReturnsTrue_StringIndexerAndNameMatch()
|
||||
{
|
||||
// Arrange
|
||||
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
|
||||
tagHelperBuilder.TypeName("TestTagHelper");
|
||||
|
||||
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
|
||||
builder
|
||||
.Name("test")
|
||||
.PropertyName("BoundProp")
|
||||
.TypeName("System.Collection.Generic.IDictionary<string, string>")
|
||||
.AsDictionary("prefix-test-", typeof(string).FullName);
|
||||
|
||||
var descriptor = builder.Build();
|
||||
|
||||
// Act
|
||||
var result = descriptor.ExpectsStringValue("prefix-test-key");
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpectsStringValue_ReturnsFalse_StringIndexerAndNameMismatch()
|
||||
{
|
||||
// Arrange
|
||||
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
|
||||
tagHelperBuilder.TypeName("TestTagHelper");
|
||||
|
||||
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
|
||||
builder
|
||||
.Name("test")
|
||||
.PropertyName("BoundProp")
|
||||
.TypeName("System.Collection.Generic.IDictionary<string, string>")
|
||||
.AsDictionary("prefix-test-", typeof(string).FullName);
|
||||
|
||||
var descriptor = builder.Build();
|
||||
|
||||
// Act
|
||||
var result = descriptor.ExpectsStringValue("test");
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpectsBooleanValue_ReturnsTrue_ForBooleanProperty()
|
||||
{
|
||||
// Arrange
|
||||
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
|
||||
tagHelperBuilder.TypeName("TestTagHelper");
|
||||
|
||||
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
|
||||
builder
|
||||
.Name("test")
|
||||
.PropertyName("BoundProp")
|
||||
.TypeName(typeof(bool).FullName);
|
||||
|
||||
var descriptor = builder.Build();
|
||||
|
||||
// Act
|
||||
var result = descriptor.ExpectsBooleanValue("test");
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpectsBooleanValue_ReturnsFalse_ForNonBooleanProperty()
|
||||
{
|
||||
// Arrange
|
||||
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
|
||||
tagHelperBuilder.TypeName("TestTagHelper");
|
||||
|
||||
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
|
||||
builder
|
||||
.Name("test")
|
||||
.PropertyName("BoundProp")
|
||||
.TypeName(typeof(int).FullName);
|
||||
|
||||
var descriptor = builder.Build();
|
||||
|
||||
// Act
|
||||
var result = descriptor.ExpectsBooleanValue("test");
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpectsBooleanValue_ReturnsTrue_BooleanIndexerAndNameMatch()
|
||||
{
|
||||
// Arrange
|
||||
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
|
||||
tagHelperBuilder.TypeName("TestTagHelper");
|
||||
|
||||
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
|
||||
builder
|
||||
.Name("test")
|
||||
.PropertyName("BoundProp")
|
||||
.TypeName("System.Collection.Generic.IDictionary<string, bool>")
|
||||
.AsDictionary("prefix-test-", typeof(bool).FullName);
|
||||
|
||||
var descriptor = builder.Build();
|
||||
|
||||
// Act
|
||||
var result = descriptor.ExpectsBooleanValue("prefix-test-key");
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpectsBooleanValue_ReturnsFalse_BooleanIndexerAndNameMismatch()
|
||||
{
|
||||
// Arrange
|
||||
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "TestTagHelper", "Test");
|
||||
tagHelperBuilder.TypeName("TestTagHelper");
|
||||
|
||||
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
|
||||
builder
|
||||
.Name("test")
|
||||
.PropertyName("BoundProp")
|
||||
.TypeName("System.Collection.Generic.IDictionary<string, bool>")
|
||||
.AsDictionary("prefix-test-", typeof(bool).FullName);
|
||||
|
||||
var descriptor = builder.Build();
|
||||
|
||||
// Act
|
||||
var result = descriptor.ExpectsBooleanValue("test");
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
var engine = RazorEngine.CreateEmpty(builder =>
|
||||
{
|
||||
builder.Phases.Add(phase);
|
||||
builder.Features.Add(new DefaultRazorParserOptionsFeature(designTime: false));
|
||||
builder.Features.Add(new DefaultRazorParserOptionsFeature(designTime: false, version: RazorParserOptions.LatestRazorLanguageVersion));
|
||||
});
|
||||
|
||||
var codeDocument = TestRazorCodeDocument.CreateEmpty();
|
||||
|
|
@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
var engine = RazorEngine.CreateEmpty((builder) =>
|
||||
{
|
||||
builder.Phases.Add(phase);
|
||||
builder.Features.Add(new DefaultRazorParserOptionsFeature(designTime: false));
|
||||
builder.Features.Add(new DefaultRazorParserOptionsFeature(designTime: false, version: RazorParserOptions.LatestRazorLanguageVersion));
|
||||
builder.Features.Add(new MyParserOptionsFeature());
|
||||
});
|
||||
|
||||
|
|
@ -58,9 +58,8 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
var engine = RazorEngine.CreateEmpty((builder) =>
|
||||
{
|
||||
builder.Phases.Add(phase);
|
||||
builder.Features.Add(new DefaultRazorParserOptionsFeature(designTime: false));
|
||||
builder.Features.Add(new DefaultRazorParserOptionsFeature(designTime: false, version: RazorParserOptions.LatestRazorLanguageVersion));
|
||||
builder.Features.Add(new MyParserOptionsFeature());
|
||||
|
||||
});
|
||||
|
||||
var imports = new[]
|
||||
|
|
|
|||
|
|
@ -46,6 +46,43 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
|
|||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<TagHelperDescriptor> MinimizedBooleanTagHelperDescriptors
|
||||
{
|
||||
get
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
CreateTagHelperDescriptor(
|
||||
tagName: "span",
|
||||
typeName: "SpanTagHelper",
|
||||
assemblyName: "TestAssembly"),
|
||||
CreateTagHelperDescriptor(
|
||||
tagName: "div",
|
||||
typeName: "DivTagHelper",
|
||||
assemblyName: "TestAssembly"),
|
||||
CreateTagHelperDescriptor(
|
||||
tagName: "input",
|
||||
typeName: "InputTagHelper",
|
||||
assemblyName: "TestAssembly",
|
||||
attributes: new Action<BoundAttributeDescriptorBuilder>[]
|
||||
{
|
||||
builder => builder
|
||||
.Name("value")
|
||||
.PropertyName("FooProp")
|
||||
.TypeName("System.String"),
|
||||
builder => builder
|
||||
.Name("bound")
|
||||
.PropertyName("BoundProp")
|
||||
.TypeName("System.Boolean"),
|
||||
builder => builder
|
||||
.Name("age")
|
||||
.PropertyName("AgeProp")
|
||||
.TypeName("System.Int32"),
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<TagHelperDescriptor> CssSelectorTagHelperDescriptors
|
||||
{
|
||||
get
|
||||
|
|
@ -267,6 +304,22 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
|
|||
.RequireAttributeDescriptor(attribute => attribute.Name("input-bound-required-string"))
|
||||
.RequireAttributeDescriptor(attribute => attribute.Name("input-unbound-required")),
|
||||
}),
|
||||
CreateTagHelperDescriptor(
|
||||
tagName: "div",
|
||||
typeName: "DivTagHelper",
|
||||
assemblyName: "TestAssembly",
|
||||
attributes: new Action<BoundAttributeDescriptorBuilder>[]
|
||||
{
|
||||
builder => builder
|
||||
.Name("boundbool")
|
||||
.PropertyName("BoundBoolProp")
|
||||
.TypeName(typeof(bool).FullName),
|
||||
builder => builder
|
||||
.Name("booldict")
|
||||
.PropertyName("BoolDictProp")
|
||||
.TypeName("System.Collections.Generic.IDictionary<string, bool>")
|
||||
.AsDictionaryAttribute("booldict-prefix-", typeof(bool).FullName),
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,11 +118,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
var descriptors = new[]
|
||||
{
|
||||
TagHelperDescriptorBuilder.Create("CatchAllTagHelper", "SomeAssembly")
|
||||
.TagMatchingRuleDescriptor(rule =>
|
||||
.TagMatchingRuleDescriptor(rule =>
|
||||
rule
|
||||
.RequireTagName("*")
|
||||
.RequireAttributeDescriptor(attribute => attribute.Name("bound")))
|
||||
.BoundAttributeDescriptor(attribute =>
|
||||
.BoundAttributeDescriptor(attribute =>
|
||||
attribute
|
||||
.Name("[item]")
|
||||
.PropertyName("ListItems")
|
||||
|
|
@ -222,7 +222,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
var descriptors = new TagHelperDescriptor[]
|
||||
{
|
||||
TagHelperDescriptorBuilder.Create("InputTagHelper", "SomeAssembly")
|
||||
.TagMatchingRuleDescriptor(rule =>
|
||||
.TagMatchingRuleDescriptor(rule =>
|
||||
rule
|
||||
.RequireTagName("input")
|
||||
.RequireTagStructure(TagStructure.WithoutEndTag))
|
||||
|
|
@ -320,7 +320,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
var descriptors = new TagHelperDescriptor[]
|
||||
{
|
||||
TagHelperDescriptorBuilder.Create("InputTagHelper1", "SomeAssembly")
|
||||
.TagMatchingRuleDescriptor(rule =>
|
||||
.TagMatchingRuleDescriptor(rule =>
|
||||
rule
|
||||
.RequireTagName("input")
|
||||
.RequireTagStructure(structure1))
|
||||
|
|
@ -2251,7 +2251,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
{
|
||||
TagHelperDescriptorBuilder.Create("mythTagHelper", "SomeAssembly")
|
||||
.TagMatchingRuleDescriptor(rule => rule.RequireTagName("myth"))
|
||||
.BoundAttributeDescriptor(attribute =>
|
||||
.BoundAttributeDescriptor(attribute =>
|
||||
attribute
|
||||
.Name("bound")
|
||||
.PropertyName("Bound")
|
||||
|
|
@ -3901,7 +3901,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
var descriptors = new TagHelperDescriptor[]
|
||||
{
|
||||
TagHelperDescriptorBuilder.Create("InputTagHelper1", "SomeAssembly")
|
||||
.TagMatchingRuleDescriptor(rule =>
|
||||
.TagMatchingRuleDescriptor(rule =>
|
||||
rule
|
||||
.RequireTagName("input")
|
||||
.RequireAttributeDescriptor(attribute => attribute.Name("unbound-required")))
|
||||
|
|
@ -3959,5 +3959,107 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
// Act & Assert
|
||||
EvaluateData(descriptors, documentContent, (MarkupBlock)expectedOutput, (RazorError[])expectedErrors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rewrite_UnderstandsMinimizedBooleanBoundAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var documentContent = "<input boundbool boundbooldict-key />";
|
||||
var descriptors = new TagHelperDescriptor[]
|
||||
{
|
||||
TagHelperDescriptorBuilder.Create("InputTagHelper", "SomeAssembly")
|
||||
.TagMatchingRuleDescriptor(rule =>
|
||||
rule
|
||||
.RequireTagName("input"))
|
||||
.BoundAttributeDescriptor(attribute =>
|
||||
attribute
|
||||
.Name("boundbool")
|
||||
.PropertyName("BoundBoolProp")
|
||||
.TypeName(typeof(bool).FullName))
|
||||
.BoundAttributeDescriptor(attribute =>
|
||||
attribute
|
||||
.Name("boundbooldict")
|
||||
.PropertyName("BoundBoolDictProp")
|
||||
.TypeName("System.Collections.Generic.IDictionary<string, bool>")
|
||||
.AsDictionary("boundbooldict-", typeof(bool).FullName))
|
||||
.Build(),
|
||||
};
|
||||
|
||||
var expectedOutput = new MarkupBlock(
|
||||
new MarkupTagHelperBlock(
|
||||
"input",
|
||||
TagMode.SelfClosing,
|
||||
attributes: new List<TagHelperAttributeNode>()
|
||||
{
|
||||
new TagHelperAttributeNode("boundbool", null, AttributeStructure.Minimized),
|
||||
new TagHelperAttributeNode("boundbooldict-key", null, AttributeStructure.Minimized),
|
||||
}));
|
||||
|
||||
// Act & Assert
|
||||
EvaluateData(descriptors, documentContent, expectedOutput, new RazorError[] { });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rewrite_FeatureDisabled_AddsErrorForMinimizedBooleanBoundAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var documentContent = "<input boundbool boundbooldict-key />";
|
||||
var descriptors = new TagHelperDescriptor[]
|
||||
{
|
||||
TagHelperDescriptorBuilder.Create("InputTagHelper", "SomeAssembly")
|
||||
.TagMatchingRuleDescriptor(rule =>
|
||||
rule
|
||||
.RequireTagName("input"))
|
||||
.BoundAttributeDescriptor(attribute =>
|
||||
attribute
|
||||
.Name("boundbool")
|
||||
.PropertyName("BoundBoolProp")
|
||||
.TypeName(typeof(bool).FullName))
|
||||
.BoundAttributeDescriptor(attribute =>
|
||||
attribute
|
||||
.Name("boundbooldict")
|
||||
.PropertyName("BoundBoolDictProp")
|
||||
.TypeName("System.Collections.Generic.IDictionary<string, bool>")
|
||||
.AsDictionary("boundbooldict-", typeof(bool).FullName))
|
||||
.Build(),
|
||||
};
|
||||
|
||||
var featureFlags = new TestRazorParserFeatureFlags(allowMinimizedBooleanTagHelperAttributes: false);
|
||||
|
||||
var expectedOutput = new MarkupBlock(
|
||||
new MarkupTagHelperBlock(
|
||||
"input",
|
||||
TagMode.SelfClosing,
|
||||
attributes: new List<TagHelperAttributeNode>()
|
||||
{
|
||||
new TagHelperAttributeNode("boundbool", null, AttributeStructure.Minimized),
|
||||
new TagHelperAttributeNode("boundbooldict-key", null, AttributeStructure.Minimized),
|
||||
}));
|
||||
|
||||
var expectedErrors = new[]
|
||||
{
|
||||
new RazorError(
|
||||
"Attribute 'boundbool' on tag helper element 'input' requires a value. Tag helper bound attributes of type 'System.Boolean' cannot be empty or contain only whitespace.",
|
||||
new SourceLocation(7, 0, 7),
|
||||
length: 9),
|
||||
new RazorError(
|
||||
"Attribute 'boundbooldict-key' on tag helper element 'input' requires a value. Tag helper bound attributes of type 'System.Boolean' cannot be empty or contain only whitespace.",
|
||||
new SourceLocation(17, 0, 17),
|
||||
length: 17),
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
EvaluateData(descriptors, documentContent, expectedOutput, expectedErrors, featureFlags: featureFlags);
|
||||
}
|
||||
|
||||
private class TestRazorParserFeatureFlags : RazorParserFeatureFlags
|
||||
{
|
||||
public TestRazorParserFeatureFlags(bool allowMinimizedBooleanTagHelperAttributes)
|
||||
{
|
||||
AllowMinimizedBooleanTagHelperAttributes = allowMinimizedBooleanTagHelperAttributes;
|
||||
}
|
||||
|
||||
public override bool AllowMinimizedBooleanTagHelperAttributes { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
var errorSink = new ErrorSink();
|
||||
var parseResult = ParseDocument(documentContent);
|
||||
var document = parseResult.Root;
|
||||
var parseTreeRewriter = new TagHelperParseTreeRewriter(null, Enumerable.Empty<TagHelperDescriptor>());
|
||||
var parseTreeRewriter = new TagHelperParseTreeRewriter(null, Enumerable.Empty<TagHelperDescriptor>(), parseResult.Options.FeatureFlags);
|
||||
|
||||
// Assert - Guard
|
||||
var rootBlock = Assert.IsType<Block>(document);
|
||||
|
|
|
|||
|
|
@ -51,11 +51,16 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
string documentContent,
|
||||
MarkupBlock expectedOutput,
|
||||
IEnumerable<RazorError> expectedErrors,
|
||||
string tagHelperPrefix = null)
|
||||
string tagHelperPrefix = null,
|
||||
RazorParserFeatureFlags featureFlags = null)
|
||||
{
|
||||
var syntaxTree = ParseDocument(documentContent);
|
||||
var errorSink = new ErrorSink();
|
||||
var parseTreeRewriter = new TagHelperParseTreeRewriter(tagHelperPrefix, descriptors);
|
||||
var parseTreeRewriter = new TagHelperParseTreeRewriter(
|
||||
tagHelperPrefix,
|
||||
descriptors,
|
||||
featureFlags ?? syntaxTree.Options.FeatureFlags);
|
||||
|
||||
var actualTree = parseTreeRewriter.Rewrite(syntaxTree.Root, errorSink);
|
||||
|
||||
var allErrors = syntaxTree.Diagnostics.Concat(errorSink.Errors.Select(error => RazorDiagnostic.Create(error)));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
public class RazorParserFeatureFlagsTest
|
||||
{
|
||||
[Fact]
|
||||
public void Create_LatestVersion_AllowsMinimizedBooleanTagHelperAttributes()
|
||||
{
|
||||
// Arrange & Act
|
||||
var context = RazorParserFeatureFlags.Create(RazorLanguageVersion.Version2_1);
|
||||
|
||||
// Assert
|
||||
Assert.True(context.AllowMinimizedBooleanTagHelperAttributes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_OlderVersion_DoesNotAllowMinimizedBooleanTagHelperAttributes()
|
||||
{
|
||||
// Arrange & Act
|
||||
var context = RazorParserFeatureFlags.Create(RazorLanguageVersion.Version1_1);
|
||||
|
||||
// Assert
|
||||
Assert.False(context.AllowMinimizedBooleanTagHelperAttributes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_UnknownVersion_Throws()
|
||||
{
|
||||
// Arrange, Act & Assert
|
||||
var exception = Assert.Throws<ArgumentException>(
|
||||
() => RazorParserFeatureFlags.Create(0));
|
||||
|
||||
Assert.Equal("Provided value for razor language version is unsupported or invalid: '0'.", exception.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,4 +15,6 @@
|
|||
input-unbound-required="hello2"
|
||||
catchall-unbound-required
|
||||
input-bound-required-string="world" />
|
||||
<div boundbool booldict-prefix-key></div>
|
||||
<div>Tag helper with unmatched bound boolean attributes.</div>
|
||||
</p>
|
||||
|
|
@ -7,6 +7,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests.TestFiles
|
|||
{
|
||||
private global::TestNamespace.CatchAllTagHelper __TestNamespace_CatchAllTagHelper;
|
||||
private global::TestNamespace.InputTagHelper __TestNamespace_InputTagHelper;
|
||||
private global::DivTagHelper __DivTagHelper;
|
||||
#pragma warning disable 219
|
||||
private void __RazorDirectiveTokenHelpers__() {
|
||||
((System.Action)(() => {
|
||||
|
|
@ -32,6 +33,10 @@ global::System.Object __typeHelper = "*, TestAssembly";
|
|||
__TestNamespace_InputTagHelper = CreateTagHelper<global::TestNamespace.InputTagHelper>();
|
||||
__TestNamespace_CatchAllTagHelper = CreateTagHelper<global::TestNamespace.CatchAllTagHelper>();
|
||||
__TestNamespace_InputTagHelper.BoundRequiredString = "world";
|
||||
__DivTagHelper = CreateTagHelper<global::DivTagHelper>();
|
||||
__DivTagHelper.BoundBoolProp = true;
|
||||
__DivTagHelper.BoolDictProp["key"] = true;
|
||||
__DivTagHelper = CreateTagHelper<global::DivTagHelper>();
|
||||
__TestNamespace_CatchAllTagHelper = CreateTagHelper<global::TestNamespace.CatchAllTagHelper>();
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ Document -
|
|||
DefaultTagHelperRuntime -
|
||||
FieldDeclaration - - private - global::TestNamespace.CatchAllTagHelper - __TestNamespace_CatchAllTagHelper
|
||||
FieldDeclaration - - private - global::TestNamespace.InputTagHelper - __TestNamespace_InputTagHelper
|
||||
FieldDeclaration - - private - global::DivTagHelper - __DivTagHelper
|
||||
DesignTimeDirective -
|
||||
DirectiveToken - (14:0,14 [17] MinimizedTagHelpers.cshtml) - "*, TestAssembly"
|
||||
CSharpCode -
|
||||
|
|
@ -15,7 +16,7 @@ Document -
|
|||
MethodDeclaration - - public async - System.Threading.Tasks.Task - ExecuteAsync
|
||||
HtmlContent - (31:0,31 [4] MinimizedTagHelpers.cshtml)
|
||||
IntermediateToken - (31:0,31 [4] MinimizedTagHelpers.cshtml) - Html - \n\n
|
||||
TagHelper - (35:2,0 [647] MinimizedTagHelpers.cshtml) - p - TagMode.StartTagAndEndTag
|
||||
TagHelper - (35:2,0 [762] MinimizedTagHelpers.cshtml) - p - TagMode.StartTagAndEndTag
|
||||
DefaultTagHelperBody -
|
||||
HtmlContent - (64:2,29 [34] MinimizedTagHelpers.cshtml)
|
||||
IntermediateToken - (64:2,29 [6] MinimizedTagHelpers.cshtml) - Html - \n
|
||||
|
|
@ -84,8 +85,24 @@ Document -
|
|||
HtmlContent - (667:16,40 [5] MinimizedTagHelpers.cshtml)
|
||||
IntermediateToken - (667:16,40 [5] MinimizedTagHelpers.cshtml) - Html - world
|
||||
DefaultTagHelperExecute -
|
||||
HtmlContent - (676:16,49 [2] MinimizedTagHelpers.cshtml)
|
||||
IntermediateToken - (676:16,49 [2] MinimizedTagHelpers.cshtml) - Html - \n
|
||||
HtmlContent - (676:16,49 [6] MinimizedTagHelpers.cshtml)
|
||||
IntermediateToken - (676:16,49 [6] MinimizedTagHelpers.cshtml) - Html - \n
|
||||
TagHelper - (682:17,4 [41] MinimizedTagHelpers.cshtml) - div - TagMode.StartTagAndEndTag
|
||||
DefaultTagHelperBody -
|
||||
DefaultTagHelperCreate - - DivTagHelper
|
||||
DefaultTagHelperProperty - - boundbool - bool DivTagHelper.BoundBoolProp - HtmlAttributeValueStyle.Minimized
|
||||
DefaultTagHelperProperty - - booldict-prefix-key - System.Collections.Generic.IDictionary<string, bool> DivTagHelper.BoolDictProp - HtmlAttributeValueStyle.Minimized
|
||||
DefaultTagHelperExecute -
|
||||
HtmlContent - (723:17,45 [6] MinimizedTagHelpers.cshtml)
|
||||
IntermediateToken - (723:17,45 [6] MinimizedTagHelpers.cshtml) - Html - \n
|
||||
TagHelper - (729:18,4 [62] MinimizedTagHelpers.cshtml) - div - TagMode.StartTagAndEndTag
|
||||
DefaultTagHelperBody -
|
||||
HtmlContent - (734:18,9 [51] MinimizedTagHelpers.cshtml)
|
||||
IntermediateToken - (734:18,9 [51] MinimizedTagHelpers.cshtml) - Html - Tag helper with unmatched bound boolean attributes.
|
||||
DefaultTagHelperCreate - - DivTagHelper
|
||||
DefaultTagHelperExecute -
|
||||
HtmlContent - (791:18,66 [2] MinimizedTagHelpers.cshtml)
|
||||
IntermediateToken - (791:18,66 [2] MinimizedTagHelpers.cshtml) - Html - \n
|
||||
DefaultTagHelperCreate - - TestNamespace.CatchAllTagHelper
|
||||
DefaultTagHelperHtmlAttribute - - catchall-unbound-required - HtmlAttributeValueStyle.Minimized
|
||||
DefaultTagHelperExecute -
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
Source Location: (14:0,14 [17] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MinimizedTagHelpers.cshtml)
|
||||
|"*, TestAssembly"|
|
||||
Generated Location: (603:12,37 [17] )
|
||||
Generated Location: (657:13,37 [17] )
|
||||
|"*, TestAssembly"|
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MinimizedTagHelpers.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "cc93b363a32adf077cc406265e403db466e4ae7d"
|
||||
#pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/MinimizedTagHelpers.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "ab8c9a5af38d07138a55ae82942b4a97fe3c9025"
|
||||
// <auto-generated/>
|
||||
#pragma warning disable 1591
|
||||
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests.TestFiles
|
||||
|
|
@ -33,6 +33,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests.TestFiles
|
|||
}
|
||||
private global::TestNamespace.CatchAllTagHelper __TestNamespace_CatchAllTagHelper;
|
||||
private global::TestNamespace.InputTagHelper __TestNamespace_InputTagHelper;
|
||||
private global::DivTagHelper __DivTagHelper;
|
||||
#pragma warning disable 1998
|
||||
public async System.Threading.Tasks.Task ExecuteAsync()
|
||||
{
|
||||
|
|
@ -128,6 +129,41 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests.TestFiles
|
|||
}
|
||||
Write(__tagHelperExecutionContext.Output);
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||
WriteLiteral("\r\n ");
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("div", global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode.StartTagAndEndTag, "test", async() => {
|
||||
}
|
||||
);
|
||||
__DivTagHelper = CreateTagHelper<global::DivTagHelper>();
|
||||
__tagHelperExecutionContext.Add(__DivTagHelper);
|
||||
__DivTagHelper.BoundBoolProp = true;
|
||||
__tagHelperExecutionContext.AddTagHelperAttribute("boundbool", __DivTagHelper.BoundBoolProp, global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.Minimized);
|
||||
if (__DivTagHelper.BoolDictProp == null)
|
||||
{
|
||||
throw new InvalidOperationException(InvalidTagHelperIndexerAssignment("booldict-prefix-key", "DivTagHelper", "BoolDictProp"));
|
||||
}
|
||||
__DivTagHelper.BoolDictProp["key"] = true;
|
||||
__tagHelperExecutionContext.AddTagHelperAttribute("booldict-prefix-key", __DivTagHelper.BoolDictProp["key"], global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.Minimized);
|
||||
await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||
if (!__tagHelperExecutionContext.Output.IsContentModified)
|
||||
{
|
||||
await __tagHelperExecutionContext.SetOutputContentAsync();
|
||||
}
|
||||
Write(__tagHelperExecutionContext.Output);
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||
WriteLiteral("\r\n ");
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("div", global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode.StartTagAndEndTag, "test", async() => {
|
||||
WriteLiteral("Tag helper with unmatched bound boolean attributes.");
|
||||
}
|
||||
);
|
||||
__DivTagHelper = CreateTagHelper<global::DivTagHelper>();
|
||||
__tagHelperExecutionContext.Add(__DivTagHelper);
|
||||
await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
|
||||
if (!__tagHelperExecutionContext.Output.IsContentModified)
|
||||
{
|
||||
await __tagHelperExecutionContext.SetOutputContentAsync();
|
||||
}
|
||||
Write(__tagHelperExecutionContext.Output);
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||
WriteLiteral("\r\n");
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,10 +11,11 @@ Document -
|
|||
DefaultTagHelperRuntime -
|
||||
FieldDeclaration - - private - global::TestNamespace.CatchAllTagHelper - __TestNamespace_CatchAllTagHelper
|
||||
FieldDeclaration - - private - global::TestNamespace.InputTagHelper - __TestNamespace_InputTagHelper
|
||||
FieldDeclaration - - private - global::DivTagHelper - __DivTagHelper
|
||||
MethodDeclaration - - public async - System.Threading.Tasks.Task - ExecuteAsync
|
||||
HtmlContent - (33:1,0 [2] MinimizedTagHelpers.cshtml)
|
||||
IntermediateToken - (33:1,0 [2] MinimizedTagHelpers.cshtml) - Html - \n
|
||||
TagHelper - (35:2,0 [647] MinimizedTagHelpers.cshtml) - p - TagMode.StartTagAndEndTag
|
||||
TagHelper - (35:2,0 [762] MinimizedTagHelpers.cshtml) - p - TagMode.StartTagAndEndTag
|
||||
DefaultTagHelperBody -
|
||||
HtmlContent - (64:2,29 [34] MinimizedTagHelpers.cshtml)
|
||||
IntermediateToken - (64:2,29 [6] MinimizedTagHelpers.cshtml) - Html - \n
|
||||
|
|
@ -63,8 +64,24 @@ Document -
|
|||
DefaultTagHelperHtmlAttribute - - catchall-unbound-required - HtmlAttributeValueStyle.Minimized
|
||||
PreallocatedTagHelperProperty - (667:16,40 [5] MinimizedTagHelpers.cshtml) - __tagHelperAttribute_6 - input-bound-required-string - BoundRequiredString
|
||||
DefaultTagHelperExecute -
|
||||
HtmlContent - (676:16,49 [2] MinimizedTagHelpers.cshtml)
|
||||
IntermediateToken - (676:16,49 [2] MinimizedTagHelpers.cshtml) - Html - \n
|
||||
HtmlContent - (676:16,49 [6] MinimizedTagHelpers.cshtml)
|
||||
IntermediateToken - (676:16,49 [6] MinimizedTagHelpers.cshtml) - Html - \n
|
||||
TagHelper - (682:17,4 [41] MinimizedTagHelpers.cshtml) - div - TagMode.StartTagAndEndTag
|
||||
DefaultTagHelperBody -
|
||||
DefaultTagHelperCreate - - DivTagHelper
|
||||
DefaultTagHelperProperty - - boundbool - bool DivTagHelper.BoundBoolProp - HtmlAttributeValueStyle.Minimized
|
||||
DefaultTagHelperProperty - - booldict-prefix-key - System.Collections.Generic.IDictionary<string, bool> DivTagHelper.BoolDictProp - HtmlAttributeValueStyle.Minimized
|
||||
DefaultTagHelperExecute -
|
||||
HtmlContent - (723:17,45 [6] MinimizedTagHelpers.cshtml)
|
||||
IntermediateToken - (723:17,45 [6] MinimizedTagHelpers.cshtml) - Html - \n
|
||||
TagHelper - (729:18,4 [62] MinimizedTagHelpers.cshtml) - div - TagMode.StartTagAndEndTag
|
||||
DefaultTagHelperBody -
|
||||
HtmlContent - (734:18,9 [51] MinimizedTagHelpers.cshtml)
|
||||
IntermediateToken - (734:18,9 [51] MinimizedTagHelpers.cshtml) - Html - Tag helper with unmatched bound boolean attributes.
|
||||
DefaultTagHelperCreate - - DivTagHelper
|
||||
DefaultTagHelperExecute -
|
||||
HtmlContent - (791:18,66 [2] MinimizedTagHelpers.cshtml)
|
||||
IntermediateToken - (791:18,66 [2] MinimizedTagHelpers.cshtml) - Html - \n
|
||||
DefaultTagHelperCreate - - TestNamespace.CatchAllTagHelper
|
||||
DefaultTagHelperHtmlAttribute - - catchall-unbound-required - HtmlAttributeValueStyle.Minimized
|
||||
DefaultTagHelperExecute -
|
||||
|
|
|
|||
Loading…
Reference in New Issue