Skip to content

Commit

Permalink
Adding support for conversion operators to the ConstantAttributeAnaly…
Browse files Browse the repository at this point in the history
…zer (#915)
  • Loading branch information
JeffAshton authored Aug 18, 2023
1 parent a207169 commit 58ea689
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 9 deletions.
48 changes: 48 additions & 0 deletions src/D2L.CodeStyle.Analyzers/ApiUsage/ConstantAttributeAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ CompilationStartAnalysisContext context
),
OperationKind.Argument
);

context.RegisterOperationAction(
ctx => AnalyzeConversion(
ctx,
(IConversionOperation)ctx.Operation,
constantAttribute
),
OperationKind.Conversion
);
}

private static void AnalyzeParameter(
Expand Down Expand Up @@ -119,6 +128,45 @@ ISymbol constantAttribute
);
}

private static void AnalyzeConversion(
OperationAnalysisContext context,
IConversionOperation conversion,
INamedTypeSymbol constantAttribute
) {

IMethodSymbol @operator = conversion.OperatorMethod;
if( @operator is null ) {
return;
}
if( @operator.Parameters.Length != 1 ) {
return;
}

// Operator parameter is not [Constant], so do nothing
IParameterSymbol parameter = @operator.Parameters[ 0 ];
if( !HasAttribute( parameter, constantAttribute ) ) {
return;
}

// Operand is a constant value, so trust it
IOperation operand = conversion.Operand;
if( operand.ConstantValue.HasValue ) {
return;
}

// Operand was defined as [Constant] already, so trust it
ISymbol operandSymbol = conversion.SemanticModel.GetSymbolInfo( operand.Syntax ).Symbol;
if( operandSymbol is not null && HasAttribute( operandSymbol, constantAttribute ) ) {
return;
}

// Operand is not constant, so report it
context.ReportDiagnostic(
descriptor: Diagnostics.NonConstantPassedToConstantParameter,
location: operand.Syntax.GetLocation(),
messageArgs: new[] { parameter.Name }
);
}

/// <summary>
/// Check if the symbol has a specific attribute attached to it.
Expand Down
128 changes: 119 additions & 9 deletions tests/D2L.CodeStyle.Analyzers.Test/Specs/ConstantAttributeAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

using System;

namespace SpecTests
{
namespace SpecTests {

using D2L.CodeStyle.Annotations.Contract;

public sealed class Logger {
Expand All @@ -19,8 +19,8 @@ public static void OtherError( string message ) {
}
}

public sealed class Types
{
public sealed class Types {

public static void SomeMethodWithConstantParameter<T>( [Constant] T param1 ) { }
public static void SomeMethodWithParameter<T>( T param1 ) { }
public static void SomeMethodWithOneConstantParameter<T>( [Constant] T param1, T param2 ) { }
Expand All @@ -30,13 +30,44 @@ public static void SomeMethodWithTwoConstantParameters<T>( [Constant] T param1,
public interface IInterface { }
public class SomeClassImplementingInterface : IInterface { }
public static void SomeMethodWithInterfaceParameter( [Constant] IInterface /* InvalidConstantType(Interface) */ @interface /**/ ) { }
public static void SomeMethodWithOneInterfaceParameter<T>(IInterface param1, [Constant] T param2) { }
public static void SomeMethodWithOneInterfaceParameter<T>( IInterface param1, [Constant] T param2 ) { }

public readonly struct ConstantStruct {
public ConstantStruct( [Constant] string value ) {
Value = value;
}
public string Value { get; }
public static implicit operator ConstantStruct( [Constant] string value ) {
return new ConstantStruct( value );
}
public static explicit operator ConstantStruct( [Constant] bool value ) {
return new ConstantStruct( "true" );
}
}

public readonly struct NonConstantStruct {
public NonConstantStruct( string value ) {
Value = value;
}
public string Value { get; }
public static implicit operator NonConstantStruct( string value ) {
return new NonConstantStruct( value );
}
public static explicit operator NonConstantStruct( bool value ) {
return new NonConstantStruct( "true" );
}
}
}

public sealed class Tests
{
void Method()
{
public sealed class Tests {

private static class Constants {
public const bool Bool = true;
public const string String = "foo";
}

void Method() {

#region Invalid type tests
const Types.SomeClassImplementingInterface interfaceClass = new Types.SomeClassImplementingInterface { };
Types.SomeMethodWithConstantParameter<Types.IInterface>( /* NonConstantPassedToConstantParameter(param1) */ interfaceClass /**/ );
Expand Down Expand Up @@ -170,5 +201,84 @@ void Method()
Types.SomeMethodWithTwoConstantParameters<bool>( /* NonConstantPassedToConstantParameter(param1) */ variableBool /**/, /* NonConstantPassedToConstantParameter(param2) */ variableBool /**/ );
#endregion
}

#region Constructor Tests

void ConstructorTests(
[D2L.CodeStyle.Annotations.Contract.Constant] string trusted,
string untrusted
) {
const string constant = "foo";
string variable = "bar";

new Types.ConstantStruct( "abc" );
new Types.ConstantStruct( Constants.String );
new Types.ConstantStruct( constant );
new Types.ConstantStruct( trusted );
new Types.ConstantStruct( /* NonConstantPassedToConstantParameter(value) */ variable /**/ );
new Types.ConstantStruct( /* NonConstantPassedToConstantParameter(value) */ untrusted /**/ );

new Types.NonConstantStruct( "abc" );
new Types.NonConstantStruct( Constants.String );
new Types.NonConstantStruct( constant );
new Types.NonConstantStruct( trusted );
new Types.NonConstantStruct( variable );
new Types.NonConstantStruct( untrusted );
}

#endregion

#region Explicit Operator Tests

void ExplicitOperatorTests(
[D2L.CodeStyle.Annotations.Contract.Constant] bool trusted,
bool untrusted
) {
const bool constant = true;
bool variable = true;

{ Types.ConstantStruct v = (Types.ConstantStruct)true; }
{ Types.ConstantStruct v = (Types.ConstantStruct)Constants.Bool; }
{ Types.ConstantStruct v = (Types.ConstantStruct)constant; }
{ Types.ConstantStruct v = (Types.ConstantStruct)trusted; }
{ Types.ConstantStruct v = (Types.ConstantStruct) /* NonConstantPassedToConstantParameter(value) */ variable /**/; }
{ Types.ConstantStruct v = (Types.ConstantStruct) /* NonConstantPassedToConstantParameter(value) */ untrusted /**/; }

{ Types.NonConstantStruct v = (Types.NonConstantStruct)true; }
{ Types.NonConstantStruct v = (Types.NonConstantStruct)Constants.Bool; }
{ Types.NonConstantStruct v = (Types.NonConstantStruct)constant; }
{ Types.NonConstantStruct v = (Types.NonConstantStruct)trusted; }
{ Types.NonConstantStruct v = (Types.NonConstantStruct)variable; }
{ Types.NonConstantStruct v = (Types.NonConstantStruct)untrusted; }
}

#endregion

#region Implicit Operator Tests

void ImplicitOperatorTests(
[D2L.CodeStyle.Annotations.Contract.Constant] string trusted,
string untrusted
) {
const string constant = "foo";
string variable = "bar";

{ Types.ConstantStruct v = "abc"; }
{ Types.ConstantStruct v = Constants.String; }
{ Types.ConstantStruct v = constant; }
{ Types.ConstantStruct v = trusted; }
{ Types.ConstantStruct v = /* NonConstantPassedToConstantParameter(value) */ variable /**/; }
{ Types.ConstantStruct v = /* NonConstantPassedToConstantParameter(value) */ untrusted /**/; }

{ Types.NonConstantStruct v = "abc"; }
{ Types.NonConstantStruct v = Constants.String; }
{ Types.NonConstantStruct v = constant; }
{ Types.NonConstantStruct v = trusted; }
{ Types.NonConstantStruct v = variable; }
{ Types.NonConstantStruct v = untrusted; }
}

#endregion

}
}

0 comments on commit 58ea689

Please sign in to comment.