|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
namespace Microsoft.AspNetCore.Components.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ComponentParameterUsageAnalyzer : DiagnosticAnalyzer
{
public ComponentParameterUsageAnalyzer()
{
SupportedDiagnostics = ImmutableArray.Create(new[]
{
DiagnosticDescriptors.ComponentParametersShouldNotBeSetOutsideOfTheirDeclaredComponent,
});
}
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
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);
});
});
}
}
|