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);
            });
        });
    }
Esempio n. 2
0
    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;
            }

            // 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.IsParameter(symbols, property))
                    {
                        // Annotated with [Parameter]. We ignore [CascadingParameter]'s because they don't interact with tooling and don't currently have any analyzer restrictions.
                        properties.Add(property);
                    }
                }

                if (properties.Count == 0)
                {
                    return;
                }

                context.RegisterSymbolEndAction(context =>
                {
                    var captureUnmatchedValuesParameters = new List <IPropertySymbol>();

                    // Per-property validations
                    foreach (var property in properties)
                    {
                        var propertyLocation = property.Locations.FirstOrDefault();
                        if (propertyLocation == null)
                        {
                            continue;
                        }

                        if (property.DeclaredAccessibility != Accessibility.Public)
                        {
                            context.ReportDiagnostic(Diagnostic.Create(
                                                         DiagnosticDescriptors.ComponentParametersShouldBePublic,
                                                         propertyLocation,
                                                         property.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
                        }
                        else if (property.SetMethod?.DeclaredAccessibility != Accessibility.Public)
                        {
                            context.ReportDiagnostic(Diagnostic.Create(
                                                         DiagnosticDescriptors.ComponentParameterSettersShouldBePublic,
                                                         propertyLocation,
                                                         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,
                                                             propertyLocation,
                                                             property.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
                                                             property.Type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
                                                             symbols.ParameterCaptureUnmatchedValuesRuntimeType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
                            }
                        }
                        if (!IsAutoProperty(property) && !IsSameSemanticAsAutoProperty(property, context.CancellationToken))
                        {
                            context.ReportDiagnostic(Diagnostic.Create(
                                                         DiagnosticDescriptors.ComponentParametersShouldBeAutoProperties,
                                                         propertyLocation,
                                                         property.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);
        });
    }