-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Add runtime support for Blazor attribute splatting #10357
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
Merged
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
23f6ccb
Add basic tests for duplicate attributes
4d5ced4
Add AddMultipleAttributes improve RTB
630d5f6
Harden EventCallback test
b815d3c
Add new setting on ParameterAttribute
ef3a2fa
Add renderer tests for 'extra' parameters
c6aaf93
Refactor Diagnostics for more analyzers
dae4214
Simplify analyzer and fix CascadingParameter
e0245e5
Add analyzer for types
d1d8042
Add analyzer for uniqueness
2342762
Fix project file
6d5794c
Adjust name
77bf360
PR feedback on PCE and more renames
9f3ffb5
Remove unused parameter
ccd5498
Fix #10398
30c3bfe
Add E2E test
2341101
Pranavs cool feedback
f45975d
Optimize silent frame removal
3d38b3b
code check
95832c6
pr feedback
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// 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.Linq; | ||
using Microsoft.CodeAnalysis; | ||
|
||
namespace Microsoft.AspNetCore.Components.Analyzers | ||
{ | ||
internal static class ComponentFacts | ||
{ | ||
public static bool IsAnyParameter(ComponentSymbols symbols, IPropertySymbol property) | ||
{ | ||
if (symbols == null) | ||
{ | ||
throw new ArgumentNullException(nameof(symbols)); | ||
} | ||
|
||
if (property == null) | ||
{ | ||
throw new ArgumentNullException(nameof(property)); | ||
} | ||
|
||
return property.GetAttributes().Any(a => | ||
{ | ||
return a.AttributeClass == symbols.ParameterAttribute || a.AttributeClass == symbols.CascadingParameterAttribute; | ||
}); | ||
} | ||
|
||
public static bool IsParameter(ComponentSymbols symbols, IPropertySymbol property) | ||
{ | ||
if (symbols == null) | ||
{ | ||
throw new ArgumentNullException(nameof(symbols)); | ||
} | ||
|
||
if (property == null) | ||
{ | ||
throw new ArgumentNullException(nameof(property)); | ||
} | ||
|
||
return property.GetAttributes().Any(a => a.AttributeClass == symbols.ParameterAttribute); | ||
} | ||
|
||
public static bool IsParameterWithCaptureUnmatchedValues(ComponentSymbols symbols, IPropertySymbol property) | ||
{ | ||
if (symbols == null) | ||
{ | ||
throw new ArgumentNullException(nameof(symbols)); | ||
} | ||
|
||
if (property == null) | ||
{ | ||
throw new ArgumentNullException(nameof(property)); | ||
} | ||
|
||
var attribute = property.GetAttributes().FirstOrDefault(a => a.AttributeClass == symbols.ParameterAttribute); | ||
if (attribute == null) | ||
{ | ||
return false; | ||
} | ||
|
||
foreach (var kvp in attribute.NamedArguments) | ||
{ | ||
if (string.Equals(kvp.Key, ComponentsApi.ParameterAttribute.CaptureUnmatchedValues, StringComparison.Ordinal)) | ||
{ | ||
return kvp.Value.Value as bool? ?? false; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public static bool IsCascadingParameter(ComponentSymbols symbols, IPropertySymbol property) | ||
{ | ||
if (symbols == null) | ||
{ | ||
throw new ArgumentNullException(nameof(symbols)); | ||
} | ||
|
||
if (property == null) | ||
{ | ||
throw new ArgumentNullException(nameof(property)); | ||
} | ||
|
||
return property.GetAttributes().Any(a => a.AttributeClass == symbols.CascadingParameterAttribute); | ||
} | ||
} | ||
} |
111 changes: 111 additions & 0 deletions
111
src/Components/Analyzers/src/ComponentParameterAnalyzer.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// 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.Collections.Immutable; | ||
using System.Linq; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
|
||
namespace Microsoft.AspNetCore.Components.Analyzers | ||
{ | ||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public class ComponentParameterAnalyzer : DiagnosticAnalyzer | ||
{ | ||
public ComponentParameterAnalyzer() | ||
{ | ||
SupportedDiagnostics = ImmutableArray.Create(new[] | ||
{ | ||
DiagnosticDescriptors.ComponentParametersShouldNotBePublic, | ||
DiagnosticDescriptors.ComponentParameterCaptureUnmatchedValuesMustBeUnique, | ||
DiagnosticDescriptors.ComponentParameterCaptureUnmatchedValuesHasWrongType, | ||
}); | ||
} | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.RegisterCompilationStartAction(context => | ||
{ | ||
if (!ComponentSymbols.TryCreate(context.Compilation, out var symbols)) | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
// Types we need are not defined. | ||
return; | ||
} | ||
|
||
// This operates per-type because one of the validations we need has to look for duplicates | ||
// defined on the same type. | ||
context.RegisterSymbolStartAction(context => | ||
{ | ||
var properties = new List<IPropertySymbol>(); | ||
|
||
var type = (INamedTypeSymbol)context.Symbol; | ||
foreach (var member in type.GetMembers()) | ||
{ | ||
if (member is IPropertySymbol property && ComponentFacts.IsAnyParameter(symbols, property)) | ||
{ | ||
// Annotated with [Parameter] or [CascadingParameter] | ||
properties.Add(property); | ||
} | ||
} | ||
|
||
if (properties.Count == 0) | ||
{ | ||
return; | ||
} | ||
|
||
context.RegisterSymbolEndAction(context => | ||
{ | ||
var captureUnmatchedValuesParameters = new List<IPropertySymbol>(); | ||
|
||
// Per-property validations | ||
foreach (var property in properties) | ||
{ | ||
if (property.SetMethod?.DeclaredAccessibility == Accessibility.Public) | ||
{ | ||
context.ReportDiagnostic(Diagnostic.Create( | ||
DiagnosticDescriptors.ComponentParametersShouldNotBePublic, | ||
property.Locations[0], | ||
property.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat))); | ||
} | ||
|
||
if (ComponentFacts.IsParameterWithCaptureUnmatchedValues(symbols, property)) | ||
{ | ||
captureUnmatchedValuesParameters.Add(property); | ||
|
||
// Check the type, we need to be able to assign a Dictionary<string, object> | ||
var conversion = context.Compilation.ClassifyConversion(symbols.ParameterCaptureUnmatchedValuesRuntimeType, property.Type); | ||
if (!conversion.Exists || conversion.IsExplicit) | ||
{ | ||
context.ReportDiagnostic(Diagnostic.Create( | ||
DiagnosticDescriptors.ComponentParameterCaptureUnmatchedValuesHasWrongType, | ||
property.Locations[0], | ||
property.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat), | ||
property.Type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat), | ||
symbols.ParameterCaptureUnmatchedValuesRuntimeType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat))); | ||
} | ||
} | ||
} | ||
|
||
// Check if the type defines multiple CaptureUnmatchedValues parameters. Doing this outside the loop means we place the | ||
// errors on the type. | ||
if (captureUnmatchedValuesParameters.Count > 1) | ||
{ | ||
context.ReportDiagnostic(Diagnostic.Create( | ||
DiagnosticDescriptors.ComponentParameterCaptureUnmatchedValuesMustBeUnique, | ||
context.Symbol.Locations[0], | ||
type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat), | ||
Environment.NewLine, | ||
string.Join( | ||
Environment.NewLine, | ||
captureUnmatchedValuesParameters.Select(p => p.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)).OrderBy(n => n)))); | ||
} | ||
}); | ||
}, SymbolKind.NamedType); | ||
}); | ||
} | ||
} | ||
} |
76 changes: 0 additions & 76 deletions
76
src/Components/Analyzers/src/ComponentParametersShouldNotBePublicAnalyzer.cs
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// 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 Microsoft.CodeAnalysis; | ||
|
||
namespace Microsoft.AspNetCore.Components.Analyzers | ||
{ | ||
internal class ComponentSymbols | ||
{ | ||
public static bool TryCreate(Compilation compilation, out ComponentSymbols symbols) | ||
{ | ||
if (compilation == null) | ||
{ | ||
throw new ArgumentNullException(nameof(compilation)); | ||
} | ||
|
||
var parameterAttribute = compilation.GetTypeByMetadataName(ComponentsApi.ParameterAttribute.MetadataName); | ||
if (parameterAttribute == null) | ||
{ | ||
symbols = null; | ||
return false; | ||
} | ||
|
||
var cascadingParameterAttribute = compilation.GetTypeByMetadataName(ComponentsApi.CascadingParameterAttribute.MetadataName); | ||
if (cascadingParameterAttribute == null) | ||
{ | ||
symbols = null; | ||
return false; | ||
} | ||
|
||
var dictionary = compilation.GetTypeByMetadataName("System.Collections.Generic.Dictionary`2"); | ||
var @string = compilation.GetSpecialType(SpecialType.System_String); | ||
var @object = compilation.GetSpecialType(SpecialType.System_Object); | ||
if (dictionary == null || @string == null || @object == null) | ||
{ | ||
symbols = null; | ||
return false; | ||
} | ||
|
||
var parameterCaptureUnmatchedValuesRuntimeType = dictionary.Construct(@string, @object); | ||
|
||
symbols = new ComponentSymbols(parameterAttribute, cascadingParameterAttribute, parameterCaptureUnmatchedValuesRuntimeType); | ||
return true; | ||
} | ||
|
||
private ComponentSymbols( | ||
INamedTypeSymbol parameterAttribute, | ||
INamedTypeSymbol cascadingParameterAttribute, | ||
INamedTypeSymbol parameterCaptureUnmatchedValuesRuntimeType) | ||
{ | ||
ParameterAttribute = parameterAttribute; | ||
CascadingParameterAttribute = cascadingParameterAttribute; | ||
ParameterCaptureUnmatchedValuesRuntimeType = parameterCaptureUnmatchedValuesRuntimeType; | ||
} | ||
|
||
public INamedTypeSymbol ParameterAttribute { get; } | ||
|
||
// Dictionary<string, object> | ||
public INamedTypeSymbol ParameterCaptureUnmatchedValuesRuntimeType { get; } | ||
|
||
public INamedTypeSymbol CascadingParameterAttribute { get; } | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.