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)); }
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 icomponentType = compilation.GetTypeByMetadataName(ComponentsApi.IComponent.MetadataName); if (icomponentType == 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, icomponentType); return(true); }
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 => SymbolEqualityComparer.Default.Equals(a.AttributeClass, symbols.ParameterAttribute))); }
public static bool IsComponent(ComponentSymbols symbols, Compilation compilation, INamedTypeSymbol type) { if (symbols is null) { throw new ArgumentNullException(nameof(symbols)); } if (type is null) { throw new ArgumentNullException(nameof(type)); } var conversion = compilation.ClassifyConversion(symbols.IComponentType, type); if (!conversion.Exists || !conversion.IsExplicit) { return(false); } return(true); }
public override void Initialize(AnalysisContext context) { context.RegisterCompilationStartAction(context => { if (!ComponentSymbols.TryCreate(context.Compilation, out var symbols)) { // 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); }); }
public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); context.RegisterCompilationStartAction(context => { if (!ComponentSymbols.TryCreate(context.Compilation, out var symbols)) { // Types we need are not defined. return; } context.RegisterOperationBlockStartAction(startBlockContext => { startBlockContext.RegisterOperationAction(context => { IOperation leftHandSide; if (context.Operation is IAssignmentOperation assignmentOperation) { leftHandSide = assignmentOperation.Target; } else { var incrementOrDecrementOperation = (IIncrementOrDecrementOperation)context.Operation; leftHandSide = incrementOrDecrementOperation.Target; } if (leftHandSide == null) { // Malformed assignment, no left hand side. return; } if (leftHandSide.Kind != OperationKind.PropertyReference) { // We don't want to capture situations where a user does // MyOtherProperty = aComponent.SomeParameter return; } var propertyReference = (IPropertyReferenceOperation)leftHandSide; var componentProperty = (IPropertySymbol)propertyReference.Member; if (!ComponentFacts.IsParameter(symbols, componentProperty)) { // This is not a property reference that we care about, it is not decorated with [Parameter]. return; } var propertyContainingType = componentProperty.ContainingType; if (!ComponentFacts.IsComponent(symbols, context.Compilation, propertyContainingType)) { // Someone referenced a property as [Parameter] inside something that is not a component. return; } var assignmentContainingType = startBlockContext.OwningSymbol?.ContainingType; if (assignmentContainingType == null) { // Assignment location has no containing type. Most likely we're operating on malformed code, don't try and validate. return; } var conversion = context.Compilation.ClassifyConversion(propertyContainingType, assignmentContainingType); if (conversion.Exists && conversion.IsIdentity) { // The assignment is taking place inside of the declaring component. return; } if (conversion.Exists && conversion.IsExplicit) { // The assignment is taking place within the components type hierarchy. This means the user is setting this in a supported // scenario. return; } // At this point the user is referencing a component parameter outside of its declaring class. context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.ComponentParametersShouldNotBeSetOutsideOfTheirDeclaredComponent, propertyReference.Syntax.GetLocation(), propertyReference.Member.Name)); }, OperationKind.SimpleAssignment, OperationKind.CompoundAssignment, OperationKind.CoalesceAssignment, OperationKind.Increment, OperationKind.Decrement); }); }); }