Exemple #1
0
        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);
        }
Exemple #2
0
        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));
        }
Exemple #3
0
        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);
        }
Exemple #4
0
        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)));
        }
Exemple #5
0
        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);
        }
Exemple #6
0
        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);
                });
            });
        }