Skip to content

Commit

Permalink
Provide a tooling gesture that suggests values for required component…
Browse files Browse the repository at this point in the history
… parameters are not specified. (dotnet/aspnetcore#33384)

* Provide a tooling gesture that suggests values for required component parameters are not specified.

Fixes dotnet/aspnetcore#11815

Commit migrated from dotnet/aspnetcore@e02723975a97
  • Loading branch information
pranavkm authored Jun 11, 2021
1 parent 1b2a74c commit 6a52cae
Show file tree
Hide file tree
Showing 64 changed files with 1,441 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ protected BoundAttributeDescriptor(string kind)

public bool IsBooleanProperty { get; protected set; }

internal bool IsEditorRequired { get; set; }

public string Name { get; protected set; }

public string IndexerNamePrefix { get; protected set; }
Expand Down Expand Up @@ -81,4 +83,4 @@ public override int GetHashCode()
return BoundAttributeDescriptorComparer.Default.GetHashCode(this);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;
Expand Down Expand Up @@ -28,6 +28,8 @@ public abstract class BoundAttributeDescriptorBuilder

public abstract RazorDiagnosticCollection Diagnostics { get; }

internal bool IsEditorRequired { get; set; }

public virtual IReadOnlyList<BoundAttributeParameterDescriptorBuilder> BoundAttributeParameters { get; }

public virtual void BindAttributeParameter(Action<BoundAttributeParameterDescriptorBuilder> configure)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentInte
}
}

private ComponentIntermediateNode RewriteAsComponent(TagHelperIntermediateNode node, TagHelperDescriptor tagHelper)
private static ComponentIntermediateNode RewriteAsComponent(TagHelperIntermediateNode node, TagHelperDescriptor tagHelper)
{
var component = new ComponentIntermediateNode()
{
Expand All @@ -89,13 +89,54 @@ private ComponentIntermediateNode RewriteAsComponent(TagHelperIntermediateNode n
// because we see the nodes in the wrong order.
foreach (var childContent in component.ChildContents)
{
childContent.ParameterName = childContent.ParameterName ?? component.ChildContentParameterName ?? ComponentMetadata.ChildContent.DefaultParameterName;
childContent.ParameterName ??= component.ChildContentParameterName ?? ComponentMetadata.ChildContent.DefaultParameterName;
}

ValidateRequiredAttributes(node, tagHelper, component);

return component;
}

private MarkupElementIntermediateNode RewriteAsElement(TagHelperIntermediateNode node)
private static void ValidateRequiredAttributes(TagHelperIntermediateNode node, TagHelperDescriptor tagHelper, ComponentIntermediateNode intermediateNode)
{
if (intermediateNode.Children.Any(c => c is TagHelperDirectiveAttributeIntermediateNode node && (node.TagHelper?.IsSplatTagHelper() ?? false)))
{
// If there are any splat attributes, assume the user may have provided all values.
// This pass runs earlier than ComponentSplatLoweringPass, so we cannot rely on the presence of SplatIntermediateNode to make this check.
return;
}

foreach (var requiredAttribute in tagHelper.EditorRequiredAttributes)
{
if (!IsPresentAsAttribute(requiredAttribute.Name, intermediateNode))
{
intermediateNode.Diagnostics.Add(
RazorDiagnosticFactory.CreateComponent_EditorRequiredParameterNotSpecified(
node.Source ?? SourceSpan.Undefined,
intermediateNode.TagName,
requiredAttribute.Name));
}
}

static bool IsPresentAsAttribute(string attributeName, ComponentIntermediateNode intermediateNode)
{
foreach (var child in intermediateNode.Children)
{
if (child is ComponentAttributeIntermediateNode attributeNode && attributeName == attributeNode.AttributeName)
{
return true;
}
else if (child is ComponentChildContentIntermediateNode childContent && attributeName == childContent.AttributeName)
{
return true;
}
}

return false;
}
}

private static MarkupElementIntermediateNode RewriteAsElement(TagHelperIntermediateNode node)
{
var result = new MarkupElementIntermediateNode()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,10 @@ public BoundAttributeDescriptor Build()
CaseSensitive,
parameters,
new Dictionary<string, string>(Metadata),
diagnostics.ToArray());
diagnostics.ToArray())
{
IsEditorRequired = IsEditorRequired,
};

return descriptor;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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 Microsoft.AspNetCore.Razor.Language.Legacy;

namespace Microsoft.AspNetCore.Razor.Language
Expand Down Expand Up @@ -588,6 +589,17 @@ public static RazorDiagnostic CreateTagHelper_InconsistentTagStructure(SourceSpa
return RazorDiagnostic.Create(TagHelper_InconsistentTagStructure, location, firstDescriptor, secondDescriptor, tagName, nameof(TagMatchingRuleDescriptor.TagStructure));
}

internal static readonly RazorDiagnosticDescriptor Component_EditorRequiredParameterNotSpecified =
new RazorDiagnosticDescriptor(
$"{DiagnosticPrefix}2012",
() => Resources.Component_EditorRequiredParameterNotSpecified,
RazorDiagnosticSeverity.Warning);

public static RazorDiagnostic CreateComponent_EditorRequiredParameterNotSpecified(SourceSpan location, string tagName, string parameterName)
{
return RazorDiagnostic.Create(Component_EditorRequiredParameterNotSpecified, location, tagName, parameterName);
}

#endregion

#region TagHelper Errors
Expand Down
7 changes: 5 additions & 2 deletions src/Microsoft.AspNetCore.Razor.Language/src/Resources.resx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Expand Down Expand Up @@ -571,4 +571,7 @@
<data name="ParseError_Unexpected_Identifier_At_Position" xml:space="preserve">
<value>'{0}' is not valid in this position. Valid options are '{1}'</value>
</data>
</root>
<data name="Component_EditorRequiredParameterNotSpecified" xml:space="preserve">
<value>Component '{0}' expects a value for the parameter '{1}', but a value may not have been provided.</value>
</data>
</root>
26 changes: 26 additions & 0 deletions src/Microsoft.AspNetCore.Razor.Language/src/TagHelperDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace Microsoft.AspNetCore.Razor.Language
public abstract class TagHelperDescriptor : IEquatable<TagHelperDescriptor>
{
private IEnumerable<RazorDiagnostic> _allDiagnostics;
private BoundAttributeDescriptor[] _editorRequiredAttributes;

protected TagHelperDescriptor(string kind)
{
Expand Down Expand Up @@ -45,6 +46,14 @@ protected TagHelperDescriptor(string kind)
internal bool? IsComponentFullyQualifiedNameMatchCache { get; set; }
internal bool? IsChildContentTagHelperCache { get; set; }
internal ParsedTypeInformation? ParsedTypeInfo { get; set; }
internal BoundAttributeDescriptor[] EditorRequiredAttributes
{
get
{
_editorRequiredAttributes ??= GetEditorRequiredAttributes(BoundAttributes);
return _editorRequiredAttributes;
}
}

public bool HasErrors
{
Expand Down Expand Up @@ -96,6 +105,23 @@ public override int GetHashCode()
return _hashCode.Value;
}

private static BoundAttributeDescriptor[] GetEditorRequiredAttributes(IReadOnlyList<BoundAttributeDescriptor> boundAttributeDescriptors)
{
List<BoundAttributeDescriptor> editorRequiredAttributes = null;
var count = boundAttributeDescriptors.Count;
for (var i = 0; i < count; i++)
{
var attribute = boundAttributeDescriptors[i];
if (attribute.IsEditorRequired)
{
editorRequiredAttributes ??= new();
editorRequiredAttributes.Add(attribute);
}
}

return editorRequiredAttributes?.ToArray() ?? Array.Empty<BoundAttributeDescriptor>();
}

internal readonly struct ParsedTypeInformation
{
public ParsedTypeInformation(bool success, StringSegment @namespace, StringSegment typeName)
Expand Down
Loading

0 comments on commit 6a52cae

Please sign in to comment.