├── PurityAnalyzer ├── Key.snk ├── Resources │ ├── ReturnsNewObjectMethods.txt │ ├── PureTypes.txt │ ├── PureOnInvariantIFormatProviderMethods.txt │ ├── PureExceptLocallyMethods.txt │ ├── NotUsedAsObjectsTypeParameters.txt │ └── PureExceptReadLocallyMethods.txt ├── PurityType.cs ├── CreateMatchMethodsAttribute.cs ├── Impurity.cs ├── InstanceStaticCombination.cs ├── ThoughtsOnIsPureExceptLocallyAndIsPureExceptReadLocally.txt ├── CastPurityResult.cs ├── IdentifierUsageExtensionMethods.cs ├── PureLambdaConfig.cs ├── IdentifierUsage.cs ├── RecursiveIsNewlyCreatedObjectState.cs ├── MethodDescriptor.cs ├── RecursiveStateForNotUsedAsObject.cs ├── OperationExtensions.cs ├── RecursiveState.cs ├── tools │ ├── install.ps1 │ └── uninstall.ps1 ├── PurityAnalyzer.csproj ├── ThoughtsOnGetHashCode.txt ├── MethodDescriptorExtensionMethods.cs ├── InstanceStaticCombinationExtensionMethods.cs └── ExtensionMethods.cs ├── PurityAnalyzer.Vsix ├── Key.snk ├── Resources │ └── VSPackage.ico ├── source.extension.vsixmanifest ├── app.config └── VSPackage.resx ├── README.md ├── PurityAnalyzer.Tests.CompiledCsharpLib ├── NotUsedAsObjectAttribute.cs ├── ReturnsNewObjectAttribute.cs ├── IsPureExceptLocallyAttribute.cs ├── MutableDto1.cs ├── IFactoryThatDoesNotReturnNewObject.cs ├── IFactoryThatReturnsNewObject.cs ├── IsPureAttribute.cs ├── GenericClassAndTIsNotUsedAsObject.cs ├── GenericClassAndTIsUsedAsObject.cs ├── ImmutableDto1.cs ├── DoesNotUseClassTypeParameterAsObjectAttribute.cs ├── ImmutableDto1WithPureAttribute.cs ├── ImmutableDto1WithIsPureAttributeOnClass.cs ├── ClassWithGenericMethods.cs ├── GenericClassWithSomeMethodsThatUseTAsObjectAndSomeDoNot.cs ├── MutableClassWithPureMethodsExceptLocally.cs ├── MutableClassWithPurePropertiesExceptLocally.cs ├── StaticClass.cs ├── Properties │ └── AssemblyInfo.cs └── PurityAnalyzer.Tests.CompiledCsharpLib.csproj ├── PurityAnalyzer.Tests ├── IsPureAttributeOnMethod │ ├── ConstFieldTests.cs │ ├── CallingGenericMethods.cs │ ├── CustomAttributesTests.cs │ ├── ExpressionBodiesMethodsTests.cs │ ├── NonInputPropertiesTests.cs │ ├── CallingPureExceptLocallyMethodsAndPropertiesOnNewDotNetObjectsTests.cs │ ├── OverriddenMethods │ │ └── Note.txt │ ├── InstanceFieldTests.cs │ ├── DotNetMethodTests.cs │ ├── DifferentFilesTests.cs │ ├── EventTests.cs │ ├── DifferentProjectsTests.cs │ ├── HigherOrderFunctionsTests.cs │ ├── InterfaceTakingHigherOrderFunctionsTests.cs │ ├── IteratorTests.cs │ ├── CompiledFieldsOnInputParameterTests.cs │ ├── CompiledMethodCallingTests.cs │ ├── CastingAsItRelatesToObjectMethods │ │ └── Tests.cs │ ├── MethodCallingTests.cs │ ├── FieldsOnInputParameterTests.cs │ ├── CompiledPropertiesOnInputParameterTests.cs │ ├── AssumeIsPureAttributeTests.cs │ ├── CustomBinaryOperatorsWhereSecondOperandIsNumberTests.cs │ ├── StaticFieldTests.cs │ ├── CustomUnaryOperatorsTests.cs │ ├── CustomBinaryOperatorsTests.cs │ ├── CustomComparisonOperatorsTests.cs │ ├── CallingMethodsOnGenericClassesTests.cs │ ├── LinqTests.cs │ ├── InvokingStaticMethodTests.cs │ └── ArrayTests.cs ├── ReturnsNewObjectAttribute │ └── DotNetFrameworkTests.cs ├── Properties │ └── AssemblyInfo.cs ├── IsPureAttributeOnClass │ ├── FieldInitializerTests.cs │ ├── PropertyInitializerTests.cs │ └── IsPureAttributeOnClassTests.cs ├── IsPureExceptReadLocallyAttribute │ └── IsPureExceptReadLocallyAttributeOnProperty.cs ├── app.config ├── TestsRelatingToUsingOrNotUsingTAsObject.cs ├── packages.config ├── NotUsedAsObjectAttributeTests │ └── DotNetMethodsTests.cs ├── DoesNotUseClassTypeParameterAsObjectAttributeTests │ └── Tests.cs └── IsPureAttributeOnProperty │ └── IsPureAttributeOnPropertyTests.cs ├── LICENSE ├── PurityAnalyzer.sln └── .gitignore /PurityAnalyzer/Key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymassad/PurityAnalyzer/HEAD/PurityAnalyzer/Key.snk -------------------------------------------------------------------------------- /PurityAnalyzer.Vsix/Key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymassad/PurityAnalyzer/HEAD/PurityAnalyzer.Vsix/Key.snk -------------------------------------------------------------------------------- /PurityAnalyzer.Vsix/Resources/VSPackage.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ymassad/PurityAnalyzer/HEAD/PurityAnalyzer.Vsix/Resources/VSPackage.ico -------------------------------------------------------------------------------- /PurityAnalyzer/Resources/ReturnsNewObjectMethods.txt: -------------------------------------------------------------------------------- 1 | System.Xml.XmlDocument, CreateElement 2 | System.Collections.Immutable.ImmutableArray`1, SetItem(System.Int32, T) -------------------------------------------------------------------------------- /PurityAnalyzer/Resources/PureTypes.txt: -------------------------------------------------------------------------------- 1 | System.Linq.Enumerable 2 | System.Linq.IGrouping`2 3 | System.Nullable`1 4 | System.Collections.Immutable.ImmutableArray`1 5 | System.Collections.Immutable.ImmutableArray -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PurityAnalyzer 2 | 3 | PurityAnalyzer helps C# developers write pure code. For more information see the Writing Pure Code in C# article here: https://www.dotnetcurry.com/csharp/1464/pure-code-csharp 4 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/NotUsedAsObjectAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PurityAnalyzer.Tests.CompiledCsharpLib 4 | { 5 | public class NotUsedAsObjectAttribute : Attribute 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/ReturnsNewObjectAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PurityAnalyzer.Tests.CompiledCsharpLib 4 | { 5 | public class ReturnsNewObjectAttribute : Attribute 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/IsPureExceptLocallyAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PurityAnalyzer.Tests.CompiledCsharpLib 4 | { 5 | public class IsPureExceptLocallyAttribute : Attribute 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /PurityAnalyzer/PurityType.cs: -------------------------------------------------------------------------------- 1 | namespace PurityAnalyzer 2 | { 3 | public enum PurityType 4 | { 5 | Pure, 6 | PureExceptReadLocally, 7 | PureExceptLocally, 8 | IsObjectGetHashCode 9 | } 10 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/MutableDto1.cs: -------------------------------------------------------------------------------- 1 | namespace PurityAnalyzer.Tests.CompiledCsharpLib 2 | { 3 | public class MutableDto1 4 | { 5 | public int Prop1 { get; set; } 6 | 7 | public int Field1; 8 | } 9 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/IFactoryThatDoesNotReturnNewObject.cs: -------------------------------------------------------------------------------- 1 | namespace PurityAnalyzer.Tests.CompiledCsharpLib 2 | { 3 | public interface IFactoryThatDoesNotReturnNewObject 4 | { 5 | MutableClassWithPureMethodsExceptLocally Create(); 6 | } 7 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/IFactoryThatReturnsNewObject.cs: -------------------------------------------------------------------------------- 1 | namespace PurityAnalyzer.Tests.CompiledCsharpLib 2 | { 3 | public interface IFactoryThatReturnsNewObject 4 | { 5 | [ReturnsNewObject] 6 | MutableClassWithPureMethodsExceptLocally Create(); 7 | } 8 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/IsPureAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace PurityAnalyzer.Tests.CompiledCsharpLib 8 | { 9 | public class IsPureAttribute : Attribute 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/GenericClassAndTIsNotUsedAsObject.cs: -------------------------------------------------------------------------------- 1 | namespace PurityAnalyzer.Tests.CompiledCsharpLib 2 | { 3 | public static class GenericClassAndTIsNotUsedAsObject<[NotUsedAsObject]T> 4 | { 5 | [IsPure] 6 | public static void MethodThatDoesNotUseTAsObject(T input) 7 | { 8 | 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /PurityAnalyzer/CreateMatchMethodsAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PurityAnalyzer 4 | { 5 | public class CreateMatchMethodsAttribute : Attribute 6 | { 7 | public Type[] Types { get; } 8 | 9 | public CreateMatchMethodsAttribute(params Type[] types) 10 | { 11 | Types = types; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/GenericClassAndTIsUsedAsObject.cs: -------------------------------------------------------------------------------- 1 | namespace PurityAnalyzer.Tests.CompiledCsharpLib 2 | { 3 | public static class GenericClassAndTIsUsedAsObject 4 | { 5 | [IsPure] 6 | public static void MethodThatUsesTAsObject(T input) 7 | { 8 | var s = input.ToString(); 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /PurityAnalyzer/Impurity.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace PurityAnalyzer 4 | { 5 | public class Impurity 6 | { 7 | public Impurity(SyntaxNode node, string message) 8 | { 9 | Node = node; 10 | Message = message; 11 | } 12 | 13 | public SyntaxNode Node { get; } 14 | 15 | public string Message { get; } 16 | } 17 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/ImmutableDto1.cs: -------------------------------------------------------------------------------- 1 | namespace PurityAnalyzer.Tests.CompiledCsharpLib 2 | { 3 | public class ImmutableDto1 4 | { 5 | public ImmutableDto1(int prop1, int field1) 6 | { 7 | Prop1 = prop1; 8 | Field1 = field1; 9 | } 10 | 11 | public int Prop1 { get; } 12 | 13 | public readonly int Field1; 14 | } 15 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/DoesNotUseClassTypeParameterAsObjectAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PurityAnalyzer.Tests.CompiledCsharpLib 4 | { 5 | public class DoesNotUseClassTypeParameterAsObjectAttribute : Attribute 6 | { 7 | private readonly string parameterName; 8 | 9 | public DoesNotUseClassTypeParameterAsObjectAttribute(string parameterName) => this.parameterName = parameterName; 10 | } 11 | } -------------------------------------------------------------------------------- /PurityAnalyzer/InstanceStaticCombination.cs: -------------------------------------------------------------------------------- 1 | namespace PurityAnalyzer 2 | { 3 | public abstract class InstanceStaticCombination 4 | { 5 | public sealed class Instance : InstanceStaticCombination 6 | { 7 | 8 | } 9 | 10 | public sealed class Static : InstanceStaticCombination 11 | { 12 | 13 | } 14 | 15 | public sealed class InstanceAndStatic : InstanceStaticCombination 16 | { 17 | 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/ImmutableDto1WithPureAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace PurityAnalyzer.Tests.CompiledCsharpLib 2 | { 3 | public class ImmutableDto1WithIsPureAttributeOnProperty 4 | { 5 | public ImmutableDto1WithIsPureAttributeOnProperty(int prop1, int field1) 6 | { 7 | Prop1 = prop1; 8 | Field1 = field1; 9 | } 10 | 11 | [IsPure] 12 | public int Prop1 { get; } 13 | 14 | public readonly int Field1; 15 | } 16 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/ImmutableDto1WithIsPureAttributeOnClass.cs: -------------------------------------------------------------------------------- 1 | namespace PurityAnalyzer.Tests.CompiledCsharpLib 2 | { 3 | [IsPure] 4 | public class ImmutableDto1WithIsPureAttributeOnClass 5 | { 6 | public ImmutableDto1WithIsPureAttributeOnClass(int prop1, int field1) 7 | { 8 | Prop1 = prop1; 9 | Field1 = field1; 10 | } 11 | 12 | 13 | public int Prop1 { get; } 14 | 15 | public readonly int Field1; 16 | } 17 | } -------------------------------------------------------------------------------- /PurityAnalyzer/ThoughtsOnIsPureExceptLocallyAndIsPureExceptReadLocally.txt: -------------------------------------------------------------------------------- 1 | 7 May 2022: For a method to be pure-except-read-locally or pure-except-locally, the read or mutated data must be 100% owned by the object. See the MethodThatGetsALocalAutomaticPropertyOnAnObjectOfADifferentTypeStoredInPublicInstanceMutableFieldIsNotPureExceptReadLocally test. 2 | 3 | There should be no way for a pure-except-locally method to mutate global state. 4 | There should be no way for a pure-except-read-locally method to read global state. 5 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/ClassWithGenericMethods.cs: -------------------------------------------------------------------------------- 1 | namespace PurityAnalyzer.Tests.CompiledCsharpLib 2 | { 3 | public static class ClassWithGenericMethods 4 | { 5 | [IsPure] 6 | public static void MethodThatUsesTAsObject(T input) 7 | { 8 | var s = input.ToString(); 9 | } 10 | 11 | [IsPure] 12 | public static void MethodThatDoesNotUseTAsObject<[NotUsedAsObject] T>(T input) 13 | { 14 | 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/GenericClassWithSomeMethodsThatUseTAsObjectAndSomeDoNot.cs: -------------------------------------------------------------------------------- 1 | namespace PurityAnalyzer.Tests.CompiledCsharpLib 2 | { 3 | public static class GenericClassWithSomeMethodsThatUseTAsObjectAndSomeDoNot 4 | { 5 | [IsPure] 6 | public static string MethodThatUsesTAsObject(T input) => input.ToString(); 7 | 8 | [IsPure] 9 | [DoesNotUseClassTypeParameterAsObject(nameof(T))] 10 | public static string MethodThatDoesNotUseTAsObject(T input) => ""; 11 | } 12 | } -------------------------------------------------------------------------------- /PurityAnalyzer/CastPurityResult.cs: -------------------------------------------------------------------------------- 1 | namespace PurityAnalyzer 2 | { 3 | public abstract class CastPurityResult 4 | { 5 | private CastPurityResult() 6 | { 7 | } 8 | 9 | public sealed class Pure : CastPurityResult 10 | { 11 | } 12 | 13 | public sealed class Impure : CastPurityResult 14 | { 15 | public Impure(string reason) 16 | { 17 | Reason = reason; 18 | } 19 | 20 | public string Reason { get; } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/MutableClassWithPureMethodsExceptLocally.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace PurityAnalyzer.Tests.CompiledCsharpLib 8 | { 9 | public class MutableClassWithPureMethodsExceptLocally 10 | { 11 | [IsPure] 12 | public MutableClassWithPureMethodsExceptLocally() 13 | { 14 | 15 | } 16 | 17 | public int state = 0; 18 | 19 | [IsPureExceptLocally] 20 | public int Increment() => state++; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PurityAnalyzer/IdentifierUsageExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | namespace PurityAnalyzer 2 | { 3 | public static class IdentifierUsageExtensionMethods 4 | { 5 | public static bool IsRead(this IdentifierUsage usage) 6 | { 7 | return usage.Match(readFromCaseCase: _ => true, writtenToCaseCase: _ => false, readFromAndWrittenToCaseCase: _ => true); 8 | } 9 | 10 | public static bool IsWrite(this IdentifierUsage usage) 11 | { 12 | return usage.Match(readFromCaseCase: _ => false, writtenToCaseCase: _ => true, readFromAndWrittenToCaseCase: _ => true); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /PurityAnalyzer/PureLambdaConfig.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace PurityAnalyzer 4 | { 5 | public sealed class PureLambdaConfig 6 | { 7 | public PureLambdaConfig(SyntaxNode lambdaScope, string pureLambdaMethodFullClassName, string pureLambdaMethodName) 8 | { 9 | LambdaScope = lambdaScope; 10 | PureLambdaMethodFullClassName = pureLambdaMethodFullClassName; 11 | PureLambdaMethodName = pureLambdaMethodName; 12 | } 13 | 14 | public SyntaxNode LambdaScope { get; } 15 | 16 | public string PureLambdaMethodFullClassName { get; } 17 | 18 | public string PureLambdaMethodName { get; } 19 | } 20 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/ConstFieldTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 5 | { 6 | [TestFixture] 7 | public class ConstFieldTests 8 | { 9 | [Test] 10 | public void MethodThatReadsAConstValueIsPure() 11 | { 12 | string code = @" 13 | using System; 14 | 15 | public class IsPureAttribute : Attribute 16 | { 17 | } 18 | 19 | public static class Module1 20 | { 21 | const int c = 1; 22 | [IsPure] 23 | public static int DoSomething() 24 | { 25 | return c; 26 | } 27 | }"; 28 | 29 | Utilities.AssertPure(code); 30 | 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /PurityAnalyzer/IdentifierUsage.cs: -------------------------------------------------------------------------------- 1 | namespace PurityAnalyzer 2 | { 3 | public abstract class IdentifierUsage 4 | { 5 | private IdentifierUsage() 6 | { 7 | } 8 | 9 | public class ReadFromCase : IdentifierUsage 10 | { 11 | 12 | } 13 | 14 | public class WrittenToCase : IdentifierUsage 15 | { 16 | 17 | } 18 | 19 | public class ReadFromAndWrittenToCase : IdentifierUsage 20 | { 21 | 22 | } 23 | 24 | public static IdentifierUsage ReadFrom() => new ReadFromCase(); 25 | public static IdentifierUsage WrittenTo() => new WrittenToCase(); 26 | public static IdentifierUsage ReadFromAndWrittenTo() => new ReadFromAndWrittenToCase(); 27 | } 28 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/MutableClassWithPurePropertiesExceptLocally.cs: -------------------------------------------------------------------------------- 1 | namespace PurityAnalyzer.Tests.CompiledCsharpLib 2 | { 3 | public class MutableClassWithPurePropertiesExceptLocally 4 | { 5 | public int Field1; 6 | 7 | [IsPureExceptLocally] 8 | public int PureExceptLocallyProperty 9 | { 10 | get => Field1; 11 | set => Field1 = value; 12 | } 13 | 14 | public int PureExceptLocallyPropertyGetterAndSetter 15 | { 16 | [IsPureExceptLocally] 17 | get => Field1; 18 | [IsPureExceptLocally] 19 | set => Field1 = value; 20 | } 21 | 22 | [IsPure] 23 | public MutableClassWithPurePropertiesExceptLocally() 24 | { 25 | 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /PurityAnalyzer/RecursiveIsNewlyCreatedObjectState.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Microsoft.CodeAnalysis; 3 | 4 | namespace PurityAnalyzer 5 | { 6 | public sealed class RecursiveIsNewlyCreatedObjectState 7 | { 8 | public RecursiveIsNewlyCreatedObjectState(ImmutableArray variablesUnderTest) 9 | { 10 | VariablesUnderTest = variablesUnderTest; 11 | } 12 | 13 | public ImmutableArray VariablesUnderTest { get; } 14 | 15 | public RecursiveIsNewlyCreatedObjectState Add(ILocalSymbol variable) => new RecursiveIsNewlyCreatedObjectState(VariablesUnderTest.Add(variable)); 16 | 17 | public static RecursiveIsNewlyCreatedObjectState Empty() => new RecursiveIsNewlyCreatedObjectState( ImmutableArray.Empty); 18 | } 19 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/StaticClass.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace PurityAnalyzer.Tests.CompiledCsharpLib 7 | { 8 | public static class StaticClass 9 | { 10 | [IsPure] 11 | public static string PureMethod() 12 | { 13 | return ""; 14 | } 15 | 16 | private static int state = 0; 17 | 18 | public static string ImpureMethod() 19 | { 20 | return (state++).ToString(); 21 | } 22 | 23 | [ReturnsNewObject] 24 | public static MutableDto1 CreateNew() => new MutableDto1(); 25 | 26 | public static MutableDto1 ReturnExisting() => dto1; 27 | 28 | private static MutableDto1 dto1 = new MutableDto1(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /PurityAnalyzer/Resources/PureOnInvariantIFormatProviderMethods.txt: -------------------------------------------------------------------------------- 1 | System.Int32, ToString(System.String, System.IFormatProvider) 2 | System.Int16, ToString(System.String, System.IFormatProvider) 3 | System.Int64, ToString(System.String, System.IFormatProvider) 4 | System.UInt32, ToString(System.String, System.IFormatProvider) 5 | System.UInt16, ToString(System.String, System.IFormatProvider) 6 | System.UInt64, ToString(System.String, System.IFormatProvider) 7 | System.Byte, ToString(System.String, System.IFormatProvider) 8 | System.SByte, ToString(System.String, System.IFormatProvider) 9 | System.Single, ToString(System.String, System.IFormatProvider) 10 | System.Double, ToString(System.String, System.IFormatProvider) 11 | System.TimeSpan, ToString(System.String, System.IFormatProvider) 12 | System.Decimal, ToString(System.String, System.IFormatProvider) 13 | System.DateTime, ToString(System.String, System.IFormatProvider) -------------------------------------------------------------------------------- /PurityAnalyzer/MethodDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | 3 | namespace PurityAnalyzer 4 | { 5 | public abstract class MethodDescriptor 6 | { 7 | private MethodDescriptor() 8 | { 9 | } 10 | 11 | public sealed class ByName : MethodDescriptor 12 | { 13 | public ByName(string name) 14 | { 15 | Name = name; 16 | } 17 | 18 | public string Name { get; } 19 | } 20 | 21 | public sealed class ByNameAndParameterTypes : MethodDescriptor 22 | { 23 | public ByNameAndParameterTypes(string name, ImmutableArray parameterTypeNames) 24 | { 25 | Name = name; 26 | ParameterTypeNames = parameterTypeNames; 27 | } 28 | 29 | public string Name { get; } 30 | 31 | public ImmutableArray ParameterTypeNames { get; } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /PurityAnalyzer/RecursiveStateForNotUsedAsObject.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Microsoft.CodeAnalysis; 3 | 4 | namespace PurityAnalyzer 5 | { 6 | public sealed class RecursiveStateForNotUsedAsObject 7 | { 8 | public RecursiveStateForNotUsedAsObject(ImmutableArray<(IMethodSymbol method, ITypeParameterSymbol typeParameter)> itemsInStack) 9 | { 10 | ItemsInStack = itemsInStack; 11 | } 12 | 13 | public ImmutableArray<(IMethodSymbol method, ITypeParameterSymbol typeParameter)> ItemsInStack { get; } 14 | 15 | public static RecursiveStateForNotUsedAsObject Empty { get; } = new RecursiveStateForNotUsedAsObject(ImmutableArray<(IMethodSymbol method, ITypeParameterSymbol typeParameter)>.Empty); 16 | 17 | public RecursiveStateForNotUsedAsObject Add(IMethodSymbol method, ITypeParameterSymbol typeParameter) => new RecursiveStateForNotUsedAsObject(ItemsInStack.Add((method, typeParameter))); 18 | } 19 | } -------------------------------------------------------------------------------- /PurityAnalyzer/OperationExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace PurityAnalyzer 4 | { 5 | public static class OperationExtensions 6 | { 7 | public static bool IsWrite(this IOperation operation) 8 | { 9 | switch (operation.Kind) 10 | { 11 | case OperationKind.SimpleAssignment: 12 | case OperationKind.Increment: 13 | case OperationKind.Decrement: 14 | case OperationKind.CompoundAssignment: 15 | return true; 16 | default: 17 | return false; 18 | } 19 | } 20 | 21 | public static bool IsRead(this IOperation operation) 22 | { 23 | switch (operation.Kind) 24 | { 25 | case OperationKind.SimpleAssignment: 26 | return false; 27 | default: 28 | return true; 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/CallingGenericMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using NUnit.Framework; 8 | 9 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 10 | { 11 | [TestFixture] 12 | public class CallingGenericMethods 13 | { 14 | [Test] 15 | public void CallingSimpleGenericMethod() 16 | { 17 | string code = @" 18 | using System; 19 | 20 | public class IsPureAttribute : Attribute 21 | { 22 | } 23 | 24 | public class Class1 25 | { 26 | } 27 | 28 | public static class Module1 29 | { 30 | [IsPure] 31 | public static void DoSomething() 32 | { 33 | GenericMethod(new Class1()); 34 | } 35 | 36 | public static void GenericMethod(T param) 37 | { 38 | 39 | } 40 | }"; 41 | 42 | var dignostics = Utilities.RunPurityAnalyzer(code); 43 | dignostics.Length.Should().Be(0); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/CustomAttributesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using NUnit.Framework; 8 | 9 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 10 | { 11 | [TestFixture] 12 | public class CustomAttributesTests 13 | { 14 | [Test] 15 | public void MethodDecoratedWithAttributeWhoseConstructorIsImpureIsPure() 16 | { 17 | string code = @" 18 | using System; 19 | 20 | public class IsPureAttribute : Attribute 21 | { 22 | } 23 | 24 | public class Attribute1Attribute : Attribute 25 | { 26 | static int state; 27 | public Attribute1Attribute() 28 | { 29 | state++; 30 | } 31 | } 32 | 33 | public static class Module1 34 | { 35 | [IsPure] 36 | [Attribute1] 37 | public static int DoSomething() 38 | { 39 | return 1; 40 | } 41 | }"; 42 | 43 | Utilities.AssertPure(code); 44 | 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Yacoub Massad 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/ExpressionBodiesMethodsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 5 | { 6 | [TestFixture] 7 | public class ExpressionBodiesMethodsTests 8 | { 9 | [Test] 10 | public void MethodWithImpureExpressionBodyIsConsideredImpure() 11 | { 12 | string code = @" 13 | using System; 14 | 15 | public class IsPureAttribute : Attribute 16 | { 17 | } 18 | 19 | public static class Module1 20 | { 21 | static int c = 1; 22 | [IsPure] 23 | public static int DoSomething() => c++; 24 | 25 | }"; 26 | 27 | Utilities.AssertImpure(code); 28 | 29 | } 30 | 31 | [Test] 32 | public void MethodWithPureExpressionBodyIsConsideredPure() 33 | { 34 | string code = @" 35 | using System; 36 | 37 | public class IsPureAttribute : Attribute 38 | { 39 | } 40 | 41 | public static class Module1 42 | { 43 | [IsPure] 44 | public static int DoSomething() => 1; 45 | 46 | }"; 47 | 48 | Utilities.AssertPure(code); 49 | 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /PurityAnalyzer/Resources/PureExceptLocallyMethods.txt: -------------------------------------------------------------------------------- 1 | System.Xml.XmlDocument, CreateElement 2 | System.Xml.XmlElement, SetAttribute 3 | System.Array, System.Collections.IList.set_Item(System.Int32, System.Object) 4 | System.Collections.Generic.List`1, set_Item(System.Int32, T) 5 | System.Collections.Generic.List`1, set_Capacity 6 | System.Collections.Generic.List`1, Add(T) 7 | System.Collections.Generic.List`1, AddRange(System.Collections.Generic.IEnumerable`1) 8 | System.Collections.Generic.List`1, Clear() 9 | System.Collections.Generic.List`1, Insert(System.Int32, T) 10 | System.Collections.Generic.List`1, InsertRange(System.Int32, System.Collections.Generic.IEnumerable`1) 11 | System.Collections.Generic.List`1, RemoveAt(System.Int32) 12 | System.Collections.Generic.List`1, RemoveAll(System.Predicate`1) 13 | System.Collections.Generic.List`1, RemoveRange(System.Int32, System.Int32) 14 | System.Collections.Generic.List`1, Reverse() 15 | System.Collections.Generic.List`1, Reverse(System.Int32, System.Int32) 16 | System.Collections.Generic.List`1, Sort(System.Collections.Generic.Comparer`1) 17 | System.Collections.Generic.List`1, TrimExcess() 18 | System.Text.StringBuilder, Append(System.String) -------------------------------------------------------------------------------- /PurityAnalyzer/RecursiveState.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Microsoft.CodeAnalysis; 3 | 4 | namespace PurityAnalyzer 5 | { 6 | public sealed class RecursiveState 7 | { 8 | public RecursiveState( 9 | ImmutableArray<(IMethodSymbol method, PurityType purityType)> methodsInStack, 10 | ImmutableArray constructedTypesInStack) 11 | { 12 | MethodsInStack = methodsInStack; 13 | ConstructedTypesInStack = constructedTypesInStack; 14 | } 15 | 16 | public ImmutableArray<(IMethodSymbol method, PurityType purityType)> MethodsInStack { get; } 17 | 18 | public ImmutableArray ConstructedTypesInStack { get; } 19 | 20 | public RecursiveState AddMethod(IMethodSymbol method, PurityType purityType) => new RecursiveState(MethodsInStack.Add((method, purityType)), ConstructedTypesInStack); 21 | 22 | public RecursiveState AddConstructedType(INamedTypeSymbol type) => new RecursiveState(MethodsInStack, ConstructedTypesInStack.Add(type)); 23 | 24 | public static RecursiveState Empty { get; } = new RecursiveState(ImmutableArray<(IMethodSymbol method, PurityType purityType)>.Empty, ImmutableArray.Empty); 25 | } 26 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/NonInputPropertiesTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 5 | { 6 | [TestFixture] 7 | public class NonInputPropertiesTests 8 | { 9 | [Test] 10 | public void MethodThatReadsAnInCodeReadWriteStaticPropertyIsImpure() 11 | { 12 | string code = @" 13 | using System; 14 | 15 | public class IsPureAttribute : Attribute 16 | { 17 | } 18 | 19 | public static class Module1 20 | { 21 | public static string Prop1 {get;set;} 22 | 23 | [IsPure] 24 | public static string DoSomething() 25 | { 26 | return Prop1; 27 | } 28 | }"; 29 | 30 | Utilities.AssertImpure(code); 31 | 32 | } 33 | 34 | [Test] 35 | public void MethodThatReadsAnInCodeReadOnlyStaticPropertyIsPure() 36 | { 37 | string code = @" 38 | using System; 39 | 40 | public class IsPureAttribute : Attribute 41 | { 42 | } 43 | 44 | public static class Module1 45 | { 46 | public static string Prop1 {get;} = ""str""; 47 | 48 | [IsPure] 49 | public static string DoSomething() 50 | { 51 | return Prop1; 52 | } 53 | }"; 54 | 55 | Utilities.AssertPure(code); 56 | 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/ReturnsNewObjectAttribute/DotNetFrameworkTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using FluentAssertions; 9 | using Microsoft.CodeAnalysis; 10 | using NUnit.Framework; 11 | using PurityAnalyzer.Tests.CompiledCsharpLib; 12 | 13 | namespace PurityAnalyzer.Tests.ReturnsNewObjectAttribute 14 | { 15 | [TestFixture] 16 | public class DotNetFrameworkTests 17 | { 18 | [Test] 19 | public void MethodThatCallsACompiledMethodWithTheIsPureAttributeIsPure() 20 | { 21 | string code = @" 22 | using System; 23 | using System.Collections.Immutable; 24 | 25 | public class ReturnsNewObjectAttribute : Attribute 26 | { 27 | } 28 | 29 | public static class Module1 30 | { 31 | [ReturnsNewObjectAttribute] 32 | public static ImmutableArray DoSomething(ImmutableArray array) 33 | { 34 | return array.SetItem(0, 'b'); 35 | } 36 | }"; 37 | 38 | var dignostics = Utilities.RunPurityAnalyzer( 39 | code, Utilities.GetAllReferencesNeededForType(typeof(ImmutableArray<>))); 40 | 41 | dignostics.Length.Should().Be(0); 42 | 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/CallingPureExceptLocallyMethodsAndPropertiesOnNewDotNetObjectsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Xml; 7 | using FluentAssertions; 8 | using NUnit.Framework; 9 | 10 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 11 | { 12 | [TestFixture] 13 | public class CallingPureExceptLocallyMethodsAndPropertiesOnNewDotNetObjectsTests 14 | { 15 | [TestCaseSource(nameof(GetCases))] 16 | public void TestPureInvocation(string invocation) 17 | { 18 | string code = $@" 19 | using System; 20 | using System.Text; 21 | using System.Xml; 22 | 23 | public class IsPureAttribute : Attribute 24 | {{ 25 | }} 26 | 27 | public static class Module1 28 | {{ 29 | [IsPure] 30 | public static void DoSomething() 31 | {{ 32 | {invocation}; 33 | }} 34 | }}"; 35 | 36 | var dignostics = Utilities.RunPurityAnalyzer(code, Utilities.CreateFromType()); 37 | dignostics.Length.Should().Be(0); 38 | } 39 | 40 | public static IEnumerable GetCases() 41 | { 42 | yield return "new XmlDocument().CreateElement(\"ele\").SetAttribute(\"attribute\", \"value\")"; 43 | yield return "var result = new XmlDocument().OuterXml"; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /PurityAnalyzer/Resources/NotUsedAsObjectsTypeParameters.txt: -------------------------------------------------------------------------------- 1 | System.Linq.Enumerable, Where: TSource 2 | System.Linq.Enumerable, Select: TSource, TResult 3 | System.Linq.Enumerable, SelectMany: TSource,TCollection,TResult 4 | System.Linq.Enumerable, First: TSource 5 | System.Linq.Enumerable, GroupBy: TSource,TElement,TResult 6 | System.Collections.Generic.List`1, ConvertAll(System.Converter`2): container.T, TOutput 7 | System.Linq.ImmutableArrayExtensions, Select: T, TResult 8 | System.Linq.ImmutableArrayExtensions, All: T 9 | System.Linq.ImmutableArrayExtensions, Any: T 10 | System.Linq.ImmutableArrayExtensions, SelectMany: TSource, TCollection, TResult 11 | System.Linq.ImmutableArrayExtensions, Where: T 12 | System.Linq.ImmutableArrayExtensions, Aggregate: TAccumulate, TResult, T 13 | System.Linq.ImmutableArrayExtensions, ElementAt: T 14 | System.Linq.ImmutableArrayExtensions, ElementAtOrDefault: T 15 | System.Linq.ImmutableArrayExtensions, First: T 16 | System.Linq.ImmutableArrayExtensions, FirstOrDefault: T 17 | System.Linq.ImmutableArrayExtensions, Last: T 18 | System.Linq.ImmutableArrayExtensions, LastOrDefault: T 19 | System.Linq.ImmutableArrayExtensions, Single: T 20 | System.Linq.ImmutableArrayExtensions, SingleOrDefault: T 21 | System.Linq.ImmutableArrayExtensions, ToArray: T 22 | System.Collections.Immutable.ImmutableArray`1, Add: container.T 23 | System.Collections.Immutable.ImmutableArray, Create: T 24 | System.Linq.Enumerable, ToList: TSource 25 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("PurityAnalyzer.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PurityAnalyzer.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("71bc43d8-1cf6-4d82-a7cf-a80100968f17")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/OverriddenMethods/Note.txt: -------------------------------------------------------------------------------- 1 | Note about creating objects with overridden methods: 2 | Overridden methods are 3 | (1) methods with the override keyword (overriding a virtual or abstract method) 4 | (2) method belongs to an interface implementation (explicit or implicit) 5 | 6 | Before 15 June 2018: 7 | If a method creates an instance of a class that has an impure overridden method, I consider the method to be impure. 8 | The reason is that the created object might be passed to a function that takes the abstract type as a parameter and invokes the overridden method. 9 | The original method (in the base type/interface) might be pure, but the overridden one might not be. 10 | By default, I consider invoking an abstract/interface method to be pure 11 | There might be a better way to do all of this. But for now I am using these rules. 12 | 15 June 2018: 13 | As long as the constructor (instance and static) of the class is pure, then the creation of the object is a pure operation. 14 | To solve the problem described above, I consider some cast operations to be impure. 15 | 16 | 1. For a method M that returns type A to be pure: if A has pure abstract methods, then M must return an object that has these methods also pure (if it overrides these methods). 17 | 2. An upcast cast to an abstract type with pure abstract method is pure only if the object casted from has these methods also pure (overrided). 18 | 3. A downcast to a type with pure abstract methods is only legal if we can know for sure that the source object has these methods pure (only if the target type/methods sealed?) -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("PurityAnalyzer.Tests.CompiledCsharpLib")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PurityAnalyzer.Tests.CompiledCsharpLib")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("415f2ddc-9af0-409c-a124-14ee7bd3b91c")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/InstanceFieldTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 5 | { 6 | [TestFixture] 7 | public class InstanceFieldTests 8 | { 9 | [Test] 10 | public void MethodThatReadsAReadonlyInstanceFieldIsPure() 11 | { 12 | string code = @" 13 | using System; 14 | 15 | public class IsPureAttribute : Attribute 16 | { 17 | } 18 | 19 | public class Class1 20 | { 21 | readonly int c = 1; 22 | [IsPure] 23 | public int DoSomething() 24 | { 25 | return c; 26 | } 27 | }"; 28 | 29 | Utilities.AssertPure(code); 30 | 31 | } 32 | 33 | [Test] 34 | public void MethodThatReadsAReadWriteInstanceFieldIsImpure() 35 | { 36 | string code = @" 37 | using System; 38 | 39 | public class IsPureAttribute : Attribute 40 | { 41 | } 42 | 43 | public class Class1 44 | { 45 | int c = 1; 46 | [IsPure] 47 | public int DoSomething() 48 | { 49 | return c; 50 | } 51 | }"; 52 | 53 | Utilities.AssertImpure(code); 54 | 55 | } 56 | 57 | [Test] 58 | public void MethodThatWritesAnInstanceFieldIsImpure() 59 | { 60 | string code = @" 61 | using System; 62 | 63 | public class IsPureAttribute : Attribute 64 | { 65 | } 66 | 67 | public class Class1 68 | { 69 | int c = 1; 70 | [IsPure] 71 | public int DoSomething() 72 | { 73 | c = 2; 74 | return 1; 75 | } 76 | }"; 77 | 78 | Utilities.AssertImpure(code); 79 | 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /PurityAnalyzer.Vsix/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PurityAnalyzer 6 | This analyzer can help C# developers write Pure code. 7 | https://github.com/ymassad/PurityAnalyzer 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/DotNetMethodTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Xml; 8 | using FluentAssertions; 9 | using NUnit.Framework; 10 | 11 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 12 | { 13 | [TestFixture] 14 | public class DotNetMethodTests 15 | { 16 | [Test] 17 | public void TestAccessOuterXmlPropertyOnInputParameterOfTypeXmlNode() 18 | { 19 | string code = @" 20 | using System; 21 | using System.Xml; 22 | 23 | public class IsPureAttribute : Attribute 24 | { 25 | } 26 | 27 | public static class Module1 28 | { 29 | [IsPure] 30 | public static void DoSomething(XmlNode node) 31 | { 32 | var output = node.OuterXml; 33 | } 34 | }"; 35 | 36 | var dignostics = Utilities.RunPurityAnalyzer(code, Utilities.CreateFromType()); 37 | dignostics.Length.Should().Be(0); 38 | } 39 | 40 | [Test] 41 | public void TestLoopingThroughImmutableArray() 42 | { 43 | string code = @" 44 | using System; 45 | using System.Collections.Immutable; 46 | 47 | public class IsPureAttribute : Attribute 48 | { 49 | } 50 | 51 | public static class Module1 52 | { 53 | [IsPure] 54 | public static void DoSomething(ImmutableArray array) 55 | { 56 | foreach(var a in array) 57 | { 58 | } 59 | } 60 | }"; 61 | 62 | var dignostics = Utilities.RunPurityAnalyzer( 63 | code, 64 | Utilities.GetAllReferencesNeededForType(typeof(ImmutableArray<>))); 65 | dignostics.Length.Should().Be(0); 66 | } 67 | 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /PurityAnalyzer/tools/install.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | if($project.Object.SupportsPackageDependencyResolution) 4 | { 5 | if($project.Object.SupportsPackageDependencyResolution()) 6 | { 7 | # Do not install analyzers via install.ps1, instead let the project system handle it. 8 | return 9 | } 10 | } 11 | 12 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve 13 | 14 | foreach($analyzersPath in $analyzersPaths) 15 | { 16 | if (Test-Path $analyzersPath) 17 | { 18 | # Install the language agnostic analyzers. 19 | foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) 20 | { 21 | if($project.Object.AnalyzerReferences) 22 | { 23 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) 24 | } 25 | } 26 | } 27 | } 28 | 29 | # $project.Type gives the language name like (C# or VB.NET) 30 | $languageFolder = "" 31 | if($project.Type -eq "C#") 32 | { 33 | $languageFolder = "cs" 34 | } 35 | if($project.Type -eq "VB.NET") 36 | { 37 | $languageFolder = "vb" 38 | } 39 | if($languageFolder -eq "") 40 | { 41 | return 42 | } 43 | 44 | foreach($analyzersPath in $analyzersPaths) 45 | { 46 | # Install language specific analyzers. 47 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder 48 | if (Test-Path $languageAnalyzersPath) 49 | { 50 | foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) 51 | { 52 | if($project.Object.AnalyzerReferences) 53 | { 54 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/DifferentFilesTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 5 | { 6 | [TestFixture] 7 | public class DifferentFilesTests 8 | { 9 | [Test] 10 | public void TestCallingPureMethodInAnotherFile() 11 | { 12 | string file1Code = @" 13 | using System; 14 | 15 | public class IsPureAttribute : Attribute 16 | { 17 | } 18 | 19 | public static class Module1 20 | { 21 | 22 | [IsPure] 23 | public static string DoSomething() 24 | { 25 | return Module2.PureMethod(); 26 | } 27 | }"; 28 | 29 | string file2Code = @" 30 | using System; 31 | 32 | public static class Module2 33 | { 34 | public static string PureMethod() 35 | { 36 | return """"; 37 | } 38 | }"; 39 | 40 | var dignostics = Utilities.RunPurityAnalyzer(file1Code, file2Code); 41 | dignostics.Length.Should().Be(0); 42 | 43 | } 44 | 45 | [Test] 46 | public void TestCallingImpureMethodInAnotherFile() 47 | { 48 | string file1Code = @" 49 | using System; 50 | 51 | public class IsPureAttribute : Attribute 52 | { 53 | } 54 | 55 | public static class Module1 56 | { 57 | 58 | [IsPure] 59 | public static string DoSomething() 60 | { 61 | return Module2.ImpureMethod(); 62 | } 63 | }"; 64 | 65 | string file2Code = @" 66 | using System; 67 | 68 | public static class Module2 69 | { 70 | static int state = 0; 71 | 72 | public static string ImpureMethod() 73 | { 74 | state++; 75 | return """"; 76 | } 77 | }"; 78 | 79 | var dignostics = Utilities.RunPurityAnalyzer(file1Code, file2Code); 80 | dignostics.Length.Should().BePositive(); 81 | 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/EventTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using NUnit.Framework; 8 | 9 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 10 | { 11 | [TestFixture] 12 | public class EventTests 13 | { 14 | [Test] 15 | public void MethodThatRaisesAnEventIsImpure() 16 | { 17 | string code = @" 18 | using System; 19 | 20 | public class IsPureAttribute : Attribute 21 | { 22 | } 23 | 24 | public static class Module1 25 | { 26 | public static event Action myEvent; 27 | 28 | [IsPure] 29 | public static void DoSomething() 30 | { 31 | myEvent(); 32 | } 33 | }"; 34 | 35 | Utilities.AssertImpure(code); 36 | 37 | } 38 | 39 | [Test] 40 | public void MethodThatSubscribesToAnEventIsImpure() 41 | { 42 | string code = @" 43 | using System; 44 | 45 | public class IsPureAttribute : Attribute 46 | { 47 | } 48 | 49 | public static class Module1 50 | { 51 | public static event Action myEvent; 52 | 53 | [IsPure] 54 | public static void DoSomething() 55 | { 56 | myEvent += () => {}; 57 | } 58 | }"; 59 | 60 | Utilities.AssertImpure(code); 61 | 62 | } 63 | 64 | [Test] 65 | public void MethodThatUnsubscribesFromAnEventIsImpure() 66 | { 67 | string code = @" 68 | using System; 69 | 70 | public class IsPureAttribute : Attribute 71 | { 72 | } 73 | 74 | public static class Module1 75 | { 76 | public static event Action myEvent; 77 | 78 | [IsPure] 79 | public static void DoSomething() 80 | { 81 | myEvent -= () => {}; 82 | } 83 | }"; 84 | 85 | Utilities.AssertImpure(code); 86 | 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/DifferentProjectsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 5 | { 6 | [TestFixture] 7 | public class DifferentProjectsTests 8 | { 9 | [Test] 10 | public void TestCallingPureMethodInAnotherProject() 11 | { 12 | string file1Code = @" 13 | using System; 14 | 15 | public class IsPureAttribute : Attribute 16 | { 17 | } 18 | 19 | public static class Module1 20 | { 21 | 22 | [IsPure] 23 | public static string DoSomething() 24 | { 25 | return Module2.PureMethod(); 26 | } 27 | }"; 28 | 29 | string file2Code = @" 30 | using System; 31 | 32 | public static class Module2 33 | { 34 | public static string PureMethod() 35 | { 36 | return """"; 37 | } 38 | }"; 39 | 40 | var dignostics = Utilities.RunPurityAnalyzer(file1Code, file2Code, secondFileIsInDifferentProject: true); 41 | dignostics.Length.Should().Be(0); 42 | 43 | } 44 | 45 | [Test] 46 | public void TestCallingImpureMethodInAnotherProject() 47 | { 48 | string file1Code = @" 49 | using System; 50 | 51 | public class IsPureAttribute : Attribute 52 | { 53 | } 54 | 55 | public static class Module1 56 | { 57 | 58 | [IsPure] 59 | public static string DoSomething() 60 | { 61 | return Module2.ImpureMethod(); 62 | } 63 | }"; 64 | 65 | string file2Code = @" 66 | using System; 67 | 68 | public static class Module2 69 | { 70 | static int state = 0; 71 | 72 | public static string ImpureMethod() 73 | { 74 | state++; 75 | return """"; 76 | } 77 | }"; 78 | 79 | var dignostics = Utilities.RunPurityAnalyzer(file1Code, file2Code, secondFileIsInDifferentProject: true); 80 | dignostics.Length.Should().BePositive(); 81 | 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /PurityAnalyzer/Resources/PureExceptReadLocallyMethods.txt: -------------------------------------------------------------------------------- 1 | System.Xml.XmlNode, get_OuterXml 2 | System.Array, System.Collections.IList.get_Item 3 | System.Array, GetEnumerator 4 | System.Collections.Generic.List`1, GetEnumerator 5 | System.Collections.Generic.List`1, System.Collections.Generic.IEnumerable.GetEnumerator 6 | System.Collections.Generic.List`1, System.Collections.IEnumerable.GetEnumerator 7 | System.Collections.Generic.List`1, get_Capacity 8 | System.Collections.Generic.List`1, get_Count 9 | System.Collections.Generic.List`1, get_Item 10 | System.Collections.Generic.List`1, ConvertAll(System.Converter`2) 11 | System.Collections.Generic.List`1, Exists(System.Predicate`1) 12 | System.Collections.Generic.List`1, Find(System.Predicate`1) 13 | System.Collections.Generic.List`1, FindAll(System.Predicate`1) 14 | System.Collections.Generic.List`1, FindIndex(System.Predicate`1) 15 | System.Collections.Generic.List`1, FindIndex(System.Int32, System.Predicate`1) 16 | System.Collections.Generic.List`1, FindIndex(System.Int32, System.Int32, System.Predicate`1) 17 | System.Collections.Generic.List`1, Find(System.Predicate`1) 18 | System.Collections.Generic.List`1, FindLast(System.Predicate`1) 19 | System.Collections.Generic.List`1, FindLastIndex(System.Predicate`1) 20 | System.Collections.Generic.List`1, FindLastIndex(System.Int32, System.Predicate`1) 21 | System.Collections.Generic.List`1, FindLastIndex(System.Int32, System.Int32, System.Predicate`1) 22 | System.Collections.Generic.List`1, GetRange(System.Int32, System.Int32) 23 | System.Collections.Generic.List`1, ToArray() 24 | System.Collections.Generic.List`1, TrueForAll(System.Predicate`1) 25 | System.Array, GetEnumerator 26 | System.Array, System.Collections.Generic.IEnumerable.GetEnumerator 27 | System.Array, System.Collections.IEnumerable.GetEnumerator 28 | System.Text.StringBuilder, ToString() 29 | System.Collections.Immutable.ImmutableArray`1.Enumerator, get_Current 30 | System.Collections.Immutable.ImmutableArray`1.Enumerator, MoveNext -------------------------------------------------------------------------------- /PurityAnalyzer/tools/uninstall.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | if($project.Object.SupportsPackageDependencyResolution) 4 | { 5 | if($project.Object.SupportsPackageDependencyResolution()) 6 | { 7 | # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it. 8 | return 9 | } 10 | } 11 | 12 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve 13 | 14 | foreach($analyzersPath in $analyzersPaths) 15 | { 16 | # Uninstall the language agnostic analyzers. 17 | if (Test-Path $analyzersPath) 18 | { 19 | foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) 20 | { 21 | if($project.Object.AnalyzerReferences) 22 | { 23 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) 24 | } 25 | } 26 | } 27 | } 28 | 29 | # $project.Type gives the language name like (C# or VB.NET) 30 | $languageFolder = "" 31 | if($project.Type -eq "C#") 32 | { 33 | $languageFolder = "cs" 34 | } 35 | if($project.Type -eq "VB.NET") 36 | { 37 | $languageFolder = "vb" 38 | } 39 | if($languageFolder -eq "") 40 | { 41 | return 42 | } 43 | 44 | foreach($analyzersPath in $analyzersPaths) 45 | { 46 | # Uninstall language specific analyzers. 47 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder 48 | if (Test-Path $languageAnalyzersPath) 49 | { 50 | foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) 51 | { 52 | if($project.Object.AnalyzerReferences) 53 | { 54 | try 55 | { 56 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) 57 | } 58 | catch 59 | { 60 | 61 | } 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /PurityAnalyzer/PurityAnalyzer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.3 5 | false 6 | True 7 | 8 | 9 | 10 | PurityAnalyzer 11 | 1.0.0.0 12 | Yacoub Massad 13 | 14 | https://github.com/ymassad/PurityAnalyzer 15 | 16 | https://github.com/ymassad/PurityAnalyzer 17 | false 18 | PurityAnalyzer 19 | 20 | 21 | PurityAnalyzer, analyzers 22 | true 23 | true 24 | Key.snk 25 | false 26 | 0.8 27 | 28 | 29 | 30 | 7.3 31 | 32 | 33 | 34 | 7.3 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /PurityAnalyzer/ThoughtsOnGetHashCode.txt: -------------------------------------------------------------------------------- 1 | Object.GetHashCode is special. new Object().GetHashCode() is impure because each time you do this you are likely to get a new value. 2 | 3 | But which part of it is impure? new Object() or GetHashCode()? 4 | 5 | We can argue both ways. 6 | 7 | We can say that new Object() assigns a "random" hashcode to the object. Or we can say that GetHashCode() gets the "random" value. 8 | 9 | We don't want to mark new Object() as impure in C#. 10 | 11 | This is the case because new Object() is used in many places. Even when you do new Class1(), you are calling new object() under the hood. 12 | 13 | We also don't want to mark GetHashCode() as impure. But the reason for this one is more subtle. 14 | 15 | Consider the IEnumerable.Contains method. Is this method pure? It depends on the implementation of T.Equals and T.GetHashCode. 16 | 17 | In the case when T inherits from Object and does not override Equals or GetHashCode, IEnumerable.Contains is pure. 18 | 19 | If we mark Object.GetHashCode as impure, the analyzer will consider IEnumerable.Contains to be pure even if T.GetHashCode is actually impure (like updates global state). 20 | 21 | This is related to the concept of how casting is handled in PurityAnalyzer. 22 | 23 | The following is the plan: 24 | 25 | We consider new Object() to be pure. 26 | 27 | Object.GetHashCode() will have a special purity designation, a "object.GetHashCode purity". For now, let's say Object.GetHashCode() is marked with an attribute called [IsObjectGetHashCodeAttribute]. 28 | 29 | For IEnumerable.Contains to be pure, T.GetHashCode() should at least have "object.GetHashCode purity". 30 | 31 | By the way, this is also true for String.GetHashCode(). String.GetHashCode() is impure depending on UseRandomizedStringHashAlgorithm. I think we need to treat String.GetHashCode() like we treat Object.GetHashCode(). 32 | 33 | I see two levels of implementing these ideas: 34 | 35 | 1. Handle some pure cases relating to GetHashCode (complete approach): 36 | 37 | [IsPure] 38 | public static int DoSomething(Class1 o) 39 | { 40 | return o.GetHashCode(); 41 | } 42 | 43 | One idea here is to also mark parameters as [IsObjectGetHashCode]. 44 | 45 | 2. Consider those cases impure for now and just handle the following (partial approach): 46 | 47 | A method overriding Object.GetHashCode can be marked with [IsObjectGetHashCodeAttribute] and when casting to Object or to T, the source of the casting needs to have GetHashCode that has "object.GetHashCode purity". -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnClass/FieldInitializerTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnClass 5 | { 6 | [TestFixture] 7 | public class FieldInitializerTests 8 | { 9 | [Test] 10 | public void PureFieldInitializerKeepsClassPure() 11 | { 12 | string code = @" 13 | using System; 14 | 15 | public class IsPureAttribute : Attribute 16 | { 17 | } 18 | 19 | [IsPure] 20 | public class Class1 21 | { 22 | readonly int field = AnotherClass.PureMethod(); 23 | } 24 | 25 | public static class AnotherClass 26 | { 27 | public static int PureMethod() => 1; 28 | } 29 | "; 30 | 31 | Utilities.AssertPure(code); 32 | 33 | } 34 | 35 | [Test] 36 | public void ImpureFieldInitializerMakesClassImpure() 37 | { 38 | string code = @" 39 | using System; 40 | 41 | public class IsPureAttribute : Attribute 42 | { 43 | } 44 | 45 | [IsPure] 46 | public class Class1 47 | { 48 | readonly int field = AnotherClass.ImpureMethod(); 49 | } 50 | 51 | public static class AnotherClass 52 | { 53 | static int state; 54 | 55 | public static int ImpureMethod() => state++; 56 | } 57 | "; 58 | 59 | Utilities.AssertImpure(code); 60 | 61 | } 62 | 63 | [Test] 64 | public void PureStaticFieldInitializerKeepsClassPure() 65 | { 66 | string code = @" 67 | using System; 68 | 69 | public class IsPureAttribute : Attribute 70 | { 71 | } 72 | 73 | [IsPure] 74 | public class Class1 75 | { 76 | readonly static int field = AnotherClass.PureMethod(); 77 | } 78 | 79 | public static class AnotherClass 80 | { 81 | public static int PureMethod() => 1; 82 | } 83 | "; 84 | 85 | Utilities.AssertPure(code); 86 | 87 | } 88 | 89 | [Test] 90 | public void ImpureStaticFieldInitializerMakesClassImpure() 91 | { 92 | string code = @" 93 | using System; 94 | 95 | public class IsPureAttribute : Attribute 96 | { 97 | } 98 | 99 | [IsPure] 100 | public class Class1 101 | { 102 | static readonly int field = AnotherClass.ImpureMethod(); 103 | } 104 | 105 | public static class AnotherClass 106 | { 107 | static int state; 108 | 109 | public static int ImpureMethod() => state++; 110 | } 111 | 112 | "; 113 | 114 | Utilities.AssertImpure(code); 115 | 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnClass/PropertyInitializerTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnClass 5 | { 6 | [TestFixture] 7 | public class PropertyInitializerTests 8 | { 9 | [Test] 10 | public void PurePropertyInitializerKeepsClassPure() 11 | { 12 | string code = @" 13 | using System; 14 | 15 | public class IsPureAttribute : Attribute 16 | { 17 | } 18 | 19 | [IsPure] 20 | public class Class1 21 | { 22 | int property {get;} = AnotherClass.PureMethod(); 23 | } 24 | 25 | public static class AnotherClass 26 | { 27 | public static int PureMethod() => 1; 28 | } 29 | "; 30 | 31 | Utilities.AssertPure(code); 32 | 33 | } 34 | 35 | [Test] 36 | public void ImpurePropertyInitializerMakesClassImpure() 37 | { 38 | string code = @" 39 | using System; 40 | 41 | public class IsPureAttribute : Attribute 42 | { 43 | } 44 | 45 | [IsPure] 46 | public class Class1 47 | { 48 | int property {get;} = AnotherClass.ImpureMethod(); 49 | } 50 | 51 | public static class AnotherClass 52 | { 53 | static int state; 54 | 55 | public static int ImpureMethod() => state++; 56 | } 57 | "; 58 | 59 | Utilities.AssertImpure(code); 60 | 61 | } 62 | 63 | [Test] 64 | public void PureStaticPropertyInitializerKeepsClassPure() 65 | { 66 | string code = @" 67 | using System; 68 | 69 | public class IsPureAttribute : Attribute 70 | { 71 | } 72 | 73 | [IsPure] 74 | public class Class1 75 | { 76 | static int property {get;} = AnotherClass.PureMethod(); 77 | } 78 | 79 | public static class AnotherClass 80 | { 81 | public static int PureMethod() => 1; 82 | } 83 | "; 84 | 85 | Utilities.AssertPure(code); 86 | 87 | } 88 | 89 | [Test] 90 | public void ImpureStaticPropertyInitializerMakesClassImpure() 91 | { 92 | string code = @" 93 | using System; 94 | 95 | public class IsPureAttribute : Attribute 96 | { 97 | } 98 | 99 | [IsPure] 100 | public class Class1 101 | { 102 | static int property {get;} = AnotherClass.ImpureMethod(); 103 | } 104 | 105 | public static class AnotherClass 106 | { 107 | static int state; 108 | 109 | public static int ImpureMethod() => state++; 110 | } 111 | 112 | "; 113 | 114 | Utilities.AssertImpure(code); 115 | 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /PurityAnalyzer/MethodDescriptorExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.CodeAnalysis; 4 | 5 | namespace PurityAnalyzer 6 | { 7 | [CreateMatchMethods(typeof(MethodDescriptor))] 8 | public static class MethodDescriptorExtensionMethods 9 | { 10 | public static TResult Match(this PurityAnalyzer.MethodDescriptor instance, System.Func byNameAndParameterTypesCase, System.Func byNameCase) 11 | { 12 | if (instance is PurityAnalyzer.MethodDescriptor.ByNameAndParameterTypes byNameAndParameterTypes) 13 | return byNameAndParameterTypesCase(byNameAndParameterTypes); 14 | if (instance is PurityAnalyzer.MethodDescriptor.ByName byName) 15 | return byNameCase(byName); 16 | throw new System.Exception("Invalid MethodDescriptor type"); 17 | } 18 | 19 | public static void Match(this PurityAnalyzer.MethodDescriptor instance, System.Action byNameAndParameterTypesCase, System.Action byNameCase) 20 | { 21 | if (instance is PurityAnalyzer.MethodDescriptor.ByNameAndParameterTypes byNameAndParameterTypes) 22 | { 23 | byNameAndParameterTypesCase(byNameAndParameterTypes); 24 | return; 25 | } 26 | 27 | if (instance is PurityAnalyzer.MethodDescriptor.ByName byName) 28 | { 29 | byNameCase(byName); 30 | return; 31 | } 32 | 33 | throw new System.Exception("Invalid MethodDescriptor type"); 34 | } 35 | 36 | public static bool Matches(this MethodDescriptor methodDescriptor, IMethodSymbol methodSymbol) 37 | { 38 | var method = methodSymbol.OriginalDefinition; 39 | 40 | return methodDescriptor.Match( 41 | byNameCase: x => method.Name.Equals(x.Name), 42 | byNameAndParameterTypesCase: x => 43 | method.Name.Equals(x.Name) && 44 | method.Parameters.Select(p => Utils.GetFullMetaDataName(p.Type)).SequenceEqual(x.ParameterTypeNames)); 45 | } 46 | 47 | public static bool AnyMatches(this IEnumerable methods, IMethodSymbol method) 48 | { 49 | return methods.Any(x => x.Matches(method)); 50 | } 51 | } 52 | 53 | 54 | 55 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Vsix/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/HigherOrderFunctionsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 5 | { 6 | [TestFixture] 7 | public class HigherOrderFunctionsTests 8 | { 9 | [Test] 10 | public void TakeingAFunctionAsAParameterAndCallingItKeepsTheMethodPure() 11 | { 12 | string code = @" 13 | using System; 14 | 15 | public class IsPureAttribute : Attribute 16 | { 17 | } 18 | 19 | public static class Module1 20 | { 21 | [IsPure] 22 | public static string DoSomething(Func function) 23 | { 24 | return function(1); 25 | } 26 | }"; 27 | 28 | Utilities.AssertPure(code); 29 | 30 | } 31 | 32 | [Test] 33 | public void TakeingAFunctionAsAParameterAndCallingItViaInvokeKeepsTheMethodPure() 34 | { 35 | string code = @" 36 | using System; 37 | 38 | public class IsPureAttribute : Attribute 39 | { 40 | } 41 | 42 | public static class Module1 43 | { 44 | [IsPure] 45 | public static string DoSomething(Func function) 46 | { 47 | return function.Invoke(1); 48 | } 49 | }"; 50 | 51 | Utilities.AssertPure(code); 52 | 53 | } 54 | 55 | [Test] 56 | public void CallingPureHigherOrderFunctionWithAPureFunctionKeepsMethodPure() 57 | { 58 | string code = @" 59 | using System; 60 | 61 | public class IsPureAttribute : Attribute 62 | { 63 | } 64 | 65 | public static class Module1 66 | { 67 | 68 | [IsPure] 69 | public static int DoSomething() 70 | { 71 | return HigherOrderFunction(x => x); 72 | } 73 | 74 | [IsPure] 75 | public static int HigherOrderFunction(Func function) 76 | { 77 | return function.Invoke(1); 78 | } 79 | }"; 80 | 81 | Utilities.AssertPure(code); 82 | 83 | } 84 | 85 | [Test] 86 | public void CallingPureHigherOrderFunctionWithAnImpureFunctionMakesMethodImpure() 87 | { 88 | string code = @" 89 | using System; 90 | 91 | public class IsPureAttribute : Attribute 92 | { 93 | } 94 | 95 | public static class Module1 96 | { 97 | 98 | static int state = 0; 99 | 100 | [IsPure] 101 | public static string DoSomething() 102 | { 103 | return HigherOrderFunction(x => state.ToString()); 104 | } 105 | 106 | [IsPure] 107 | public static string HigherOrderFunction(Func function) 108 | { 109 | return function.Invoke(1); 110 | } 111 | }"; 112 | 113 | Utilities.AssertImpure(code); 114 | 115 | } 116 | 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /PurityAnalyzer.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PurityAnalyzer", "PurityAnalyzer\PurityAnalyzer.csproj", "{96DDBEE8-6B8E-4DD0-95E8-E40427F22F5F}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PurityAnalyzer.Vsix", "PurityAnalyzer.Vsix\PurityAnalyzer.Vsix.csproj", "{212D50E6-8EF9-43DE-B902-F166393D74EA}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PurityAnalyzer.Tests", "PurityAnalyzer.Tests\PurityAnalyzer.Tests.csproj", "{71BC43D8-1CF6-4D82-A7CF-A80100968F17}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PurityAnalyzer.Tests.CompiledCsharpLib", "PurityAnalyzer.Tests.CompiledCsharpLib\PurityAnalyzer.Tests.CompiledCsharpLib.csproj", "{415F2DDC-9AF0-409C-A124-14EE7BD3B91C}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {96DDBEE8-6B8E-4DD0-95E8-E40427F22F5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {96DDBEE8-6B8E-4DD0-95E8-E40427F22F5F}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {96DDBEE8-6B8E-4DD0-95E8-E40427F22F5F}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {96DDBEE8-6B8E-4DD0-95E8-E40427F22F5F}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {212D50E6-8EF9-43DE-B902-F166393D74EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {212D50E6-8EF9-43DE-B902-F166393D74EA}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {212D50E6-8EF9-43DE-B902-F166393D74EA}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {212D50E6-8EF9-43DE-B902-F166393D74EA}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {71BC43D8-1CF6-4D82-A7CF-A80100968F17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {71BC43D8-1CF6-4D82-A7CF-A80100968F17}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {71BC43D8-1CF6-4D82-A7CF-A80100968F17}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {71BC43D8-1CF6-4D82-A7CF-A80100968F17}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {415F2DDC-9AF0-409C-A124-14EE7BD3B91C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {415F2DDC-9AF0-409C-A124-14EE7BD3B91C}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {415F2DDC-9AF0-409C-A124-14EE7BD3B91C}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {415F2DDC-9AF0-409C-A124-14EE7BD3B91C}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {9BA37607-B160-4A11-9294-E6CCE3FF356D} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/InterfaceTakingHigherOrderFunctionsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 5 | { 6 | [TestFixture] 7 | public class InterfaceTakingHigherOrderFunctionsTests 8 | { 9 | [Test] 10 | public void TakeingAnInterfaceFunctionAsAParameterAndCallingItKeepsTheMethodPure() 11 | { 12 | string code = @" 13 | using System; 14 | 15 | public class IsPureAttribute : Attribute 16 | { 17 | } 18 | 19 | public interface IInterface 20 | { 21 | string Call(int input); 22 | } 23 | 24 | public static class Module1 25 | { 26 | [IsPure] 27 | public static string DoSomething(IInterface function) 28 | { 29 | return function.Call(1); 30 | } 31 | }"; 32 | 33 | Utilities.AssertPure(code); 34 | 35 | } 36 | 37 | [Test] 38 | public void CallingPureHigherOrderFunctionWithAPureClassKeepsMethodPure() 39 | { 40 | string code = @" 41 | using System; 42 | 43 | public class IsPureAttribute : Attribute 44 | { 45 | } 46 | 47 | public interface IInterface 48 | { 49 | int Call(int input); 50 | } 51 | 52 | public class PureClass : IInterface 53 | { 54 | public int Call(int input) 55 | { 56 | return input; 57 | } 58 | } 59 | 60 | public static class Module1 61 | { 62 | [IsPure] 63 | public static int DoSomething() 64 | { 65 | return HigherOrderFunction(new PureClass()); 66 | } 67 | 68 | [IsPure] 69 | public static int HigherOrderFunction(IInterface function) 70 | { 71 | return function.Call(1); 72 | } 73 | }"; 74 | 75 | Utilities.AssertPure(code); 76 | 77 | } 78 | 79 | [Test] 80 | public void CallingPureHigherOrderFunctionWithAnImpureFunctionMakesMethodImpure() 81 | { 82 | string code = @" 83 | using System; 84 | 85 | public class IsPureAttribute : Attribute 86 | { 87 | } 88 | 89 | public interface IInterface 90 | { 91 | string Call(int input); 92 | } 93 | 94 | public class ImpureClass : IInterface 95 | { 96 | int state = 0; 97 | 98 | public string Call(int input) 99 | { 100 | state++; 101 | 102 | return input.ToString(); 103 | } 104 | } 105 | 106 | public static class Module1 107 | { 108 | [IsPure] 109 | public static string DoSomething() 110 | { 111 | return HigherOrderFunction(new ImpureClass()); 112 | } 113 | 114 | [IsPure] 115 | public static string HigherOrderFunction(IInterface function) 116 | { 117 | return function.Call(1); 118 | } 119 | }"; 120 | 121 | Utilities.AssertImpure(code); 122 | 123 | } 124 | 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/IteratorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using NUnit.Framework; 8 | 9 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 10 | { 11 | [TestFixture] 12 | public class IteratorTests 13 | { 14 | [Test] 15 | public void IteratorMethodThatReadsAConstantFieldIsPure() 16 | { 17 | string code = @" 18 | using System; 19 | using System.Collections.Generic; 20 | 21 | public class IsPureAttribute : Attribute 22 | { 23 | } 24 | 25 | public static class Module1 26 | { 27 | const int c = 1; 28 | [IsPure] 29 | public static IEnumerable DoSomething() 30 | { 31 | yield return c; 32 | } 33 | }"; 34 | 35 | Utilities.AssertPure(code); 36 | 37 | } 38 | 39 | [Test] 40 | public void IteratorMethodThatWritesStaticFieldIsImpure() 41 | { 42 | string code = @" 43 | using System; 44 | using System.Collections.Generic; 45 | 46 | public class IsPureAttribute : Attribute 47 | { 48 | } 49 | 50 | public static class Module1 51 | { 52 | static int state = 0; 53 | 54 | [IsPure] 55 | public static IEnumerable DoSomething() 56 | { 57 | yield return 1; 58 | 59 | state++; 60 | } 61 | }"; 62 | 63 | Utilities.AssertImpure(code); 64 | 65 | } 66 | 67 | [Test] 68 | public void MethodThatCallsAnIteratorMethodThatReadsAConstantFieldIsPure() 69 | { 70 | string code = @" 71 | using System; 72 | using System.Collections.Generic; 73 | 74 | public class IsPureAttribute : Attribute 75 | { 76 | } 77 | 78 | public static class Module1 79 | { 80 | const int c = 1; 81 | 82 | [IsPure] 83 | public static object DoSomethingElse() => DoSomething(); 84 | 85 | public static IEnumerable DoSomething() 86 | { 87 | yield return c; 88 | } 89 | }"; 90 | 91 | Utilities.AssertPure(code); 92 | 93 | } 94 | 95 | [Test] 96 | public void MethodThatCallsAnIteratorMethodThatWritesStaticFieldIsImpure() 97 | { 98 | string code = @" 99 | using System; 100 | using System.Collections.Generic; 101 | 102 | public class IsPureAttribute : Attribute 103 | { 104 | } 105 | 106 | public static class Module1 107 | { 108 | static int state = 0; 109 | 110 | [IsPure] 111 | public static object DoSomethingElse() => DoSomething(); 112 | 113 | public static IEnumerable DoSomething() 114 | { 115 | yield return 1; 116 | 117 | state++; 118 | } 119 | }"; 120 | 121 | Utilities.AssertImpure(code); 122 | 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/CompiledFieldsOnInputParameterTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 5 | { 6 | [TestFixture] 7 | public class CompiledFieldsOnInputParameterTests 8 | { 9 | [Test] 10 | public void MethodThatReadsAReadOnlyFieldOnParameterWhoseTypeIsCompiledIsPure() 11 | { 12 | string code = @" 13 | using System; 14 | using PurityAnalyzer.Tests.CompiledCsharpLib; 15 | 16 | public class IsPureAttribute : Attribute 17 | { 18 | } 19 | 20 | public static class Module1 21 | { 22 | [IsPure] 23 | public static int DoSomething(ImmutableDto1 input) 24 | { 25 | return input.Field1; 26 | } 27 | }"; 28 | 29 | Utilities.AssertPure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 30 | } 31 | 32 | 33 | //Reading from mutable input is considered pure 34 | [Test] 35 | public void MethodThatReadsAReadWriteFieldOnParameterWhoseTypeIsCompiledIsPure() 36 | { 37 | string code = @" 38 | using System; 39 | using PurityAnalyzer.Tests.CompiledCsharpLib; 40 | 41 | public class IsPureAttribute : Attribute 42 | { 43 | } 44 | 45 | public static class Module1 46 | { 47 | [IsPure] 48 | public static int DoSomething(MutableDto1 input) 49 | { 50 | return input.Field1; 51 | } 52 | }"; 53 | 54 | Utilities.AssertPure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 55 | } 56 | 57 | [Test] 58 | public void MethodThatWritesAReadWriteFieldOnParameterWhoseTypeIsCompiledIsImpure() 59 | { 60 | string code = @" 61 | using System; 62 | using PurityAnalyzer.Tests.CompiledCsharpLib; 63 | 64 | public class IsPureAttribute : Attribute 65 | { 66 | } 67 | 68 | public static class Module1 69 | { 70 | [IsPure] 71 | public static string DoSomething(MutableDto1 input) 72 | { 73 | input.Field1 = 1; 74 | return """"; 75 | } 76 | }"; 77 | 78 | Utilities.AssertImpure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 79 | } 80 | 81 | [Test] 82 | public void MethodThatReadsAndWritesAReadWriteFieldOnParameterWhoseTypeIsCompiledIsImpure() 83 | { 84 | string code = @" 85 | using System; 86 | using PurityAnalyzer.Tests.CompiledCsharpLib; 87 | 88 | public class IsPureAttribute : Attribute 89 | { 90 | } 91 | 92 | public static class Module1 93 | { 94 | [IsPure] 95 | public static string DoSomething(MutableDto1 input) 96 | { 97 | input.Field1 = input.Field1 + 1; 98 | return """"; 99 | } 100 | }"; 101 | 102 | Utilities.AssertImpure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /PurityAnalyzer/InstanceStaticCombinationExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp.Syntax; 3 | 4 | namespace PurityAnalyzer 5 | { 6 | [CreateMatchMethods(typeof(InstanceStaticCombination))] 7 | public static class InstanceStaticCombinationExtensionMethods 8 | { 9 | public static TResult Match(this PurityAnalyzer.InstanceStaticCombination instance, System.Func instanceAndStaticCase, System.Func instanceCase, System.Func staticCase) 10 | { 11 | if (instance is PurityAnalyzer.InstanceStaticCombination.InstanceAndStatic instanceAndStatic) 12 | return instanceAndStaticCase(instanceAndStatic); 13 | if (instance is PurityAnalyzer.InstanceStaticCombination.Instance instance1) 14 | return instanceCase(instance1); 15 | if (instance is PurityAnalyzer.InstanceStaticCombination.Static static1) 16 | return staticCase(static1); 17 | throw new System.Exception("Invalid InstanceStaticCombination type"); 18 | } 19 | 20 | public static void Match(this PurityAnalyzer.InstanceStaticCombination instance, System.Action instanceAndStaticCase, System.Action instanceCase, System.Action staticCase) 21 | { 22 | if (instance is PurityAnalyzer.InstanceStaticCombination.InstanceAndStatic instanceAndStatic) 23 | { 24 | instanceAndStaticCase(instanceAndStatic); 25 | return; 26 | } 27 | 28 | if (instance is PurityAnalyzer.InstanceStaticCombination.Instance instance1) 29 | { 30 | instanceCase(instance1); 31 | return; 32 | } 33 | 34 | if (instance is PurityAnalyzer.InstanceStaticCombination.Static static1) 35 | { 36 | staticCase(static1); 37 | return; 38 | } 39 | 40 | throw new System.Exception("Invalid InstanceStaticCombination type"); 41 | } 42 | 43 | public static bool Matches(this InstanceStaticCombination combination, FieldDeclarationSyntax field) 44 | { 45 | return combination.Match( 46 | instanceAndStaticCase: _ => true, 47 | instanceCase: _ => !field.IsStatic(), 48 | staticCase: _ => field.IsStatic()); 49 | } 50 | 51 | public static bool Matches(this InstanceStaticCombination combination, PropertyDeclarationSyntax property) 52 | { 53 | return combination.Match( 54 | instanceAndStaticCase: _ => true, 55 | instanceCase: _ => !property.IsStatic(), 56 | staticCase: _ => property.IsStatic()); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/CompiledMethodCallingTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 5 | { 6 | [TestFixture] 7 | public class CompiledMethodCallingTests 8 | { 9 | [Test] 10 | public void MethodThatCallsACompiledMethodWithTheIsPureAttributeIsPure() 11 | { 12 | string code = @" 13 | using System; 14 | using PurityAnalyzer.Tests.CompiledCsharpLib; 15 | 16 | 17 | public class IsPureAttribute : Attribute 18 | { 19 | } 20 | 21 | public static class Module1 22 | { 23 | [IsPure] 24 | public static string DoSomething() 25 | { 26 | return StaticClass.PureMethod(); 27 | } 28 | }"; 29 | 30 | Utilities.AssertPure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 31 | 32 | } 33 | 34 | [Test] 35 | public void MethodThatCallsAnCompiledMethodThatDoesNotHaveTheIsPureAttributeIsImpure() 36 | { 37 | string code = @" 38 | using System; 39 | using PurityAnalyzer.Tests.CompiledCsharpLib; 40 | 41 | public class IsPureAttribute : Attribute 42 | { 43 | } 44 | 45 | public static class Module1 46 | { 47 | [IsPure] 48 | public static string DoSomething() 49 | { 50 | return StaticClass.ImpureMethod(); 51 | } 52 | 53 | }"; 54 | 55 | Utilities.AssertImpure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 56 | 57 | } 58 | 59 | [Test] 60 | public void MethodThatCallsALocalFunctionThatCallsACompiledMethodWithTheIsPureAttributeIsPure() 61 | { 62 | string code = @" 63 | using System; 64 | using PurityAnalyzer.Tests.CompiledCsharpLib; 65 | 66 | public class IsPureAttribute : Attribute 67 | { 68 | } 69 | 70 | public static class Module1 71 | { 72 | [IsPure] 73 | public static string DoSomething() 74 | { 75 | string DoSomethingElsePure() 76 | { 77 | return StaticClass.PureMethod(); 78 | } 79 | 80 | return DoSomethingElsePure(); 81 | } 82 | }"; 83 | 84 | Utilities.AssertPure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 85 | 86 | } 87 | 88 | [Test] 89 | public void MethodThatCallsALocalFunctionThatClassACompiledMethodThatDoesNotHaveTheIsPureAttributeIsImpure() 90 | { 91 | string code = @" 92 | using System; 93 | using PurityAnalyzer.Tests.CompiledCsharpLib; 94 | 95 | public class IsPureAttribute : Attribute 96 | { 97 | } 98 | 99 | public static class Module1 100 | { 101 | private static int state; 102 | 103 | [IsPure] 104 | public static string DoSomething() 105 | { 106 | string DoSomethingElseImpure() 107 | { 108 | return StaticClass.ImpureMethod(); 109 | } 110 | 111 | return DoSomethingElseImpure(); 112 | } 113 | }"; 114 | 115 | Utilities.AssertImpure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 116 | 117 | } 118 | 119 | 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureExceptReadLocallyAttribute/IsPureExceptReadLocallyAttributeOnProperty.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureExceptReadLocallyAttribute 5 | { 6 | [TestFixture] 7 | public class IsPureExceptReadLocallyAttributeOnProperty 8 | { 9 | [Test] 10 | public void PropertyGetterThatReadsAConstantFieldIsPureExceptReadLocally() 11 | { 12 | string code = @" 13 | using System; 14 | 15 | public class IsPureExceptReadLocallyAttribute : Attribute 16 | { 17 | } 18 | 19 | public class Class1 20 | { 21 | const int c = 1; 22 | 23 | [IsPureExceptReadLocally] 24 | public int DoSomething => c; 25 | }"; 26 | 27 | Utilities.AssertPure(code); 28 | } 29 | 30 | [Test] 31 | public void IsPureExceptReadLocallyAttributeCannotBeAppliedOnStaticProperties() 32 | { 33 | string code = @" 34 | using System; 35 | 36 | public class IsPureExceptReadLocallyAttribute : Attribute 37 | { 38 | } 39 | 40 | public class Class1 41 | { 42 | [IsPureExceptReadLocally] 43 | public static int DoSomething => 1; 44 | }"; 45 | 46 | Utilities.AssertImpure(code); 47 | 48 | } 49 | 50 | [Test] 51 | public void PropertyGetterThatReadsAReadOnlyFieldIsPureExceptReadLocally() 52 | { 53 | string code = @" 54 | using System; 55 | 56 | public class IsPureExceptReadLocallyAttribute : Attribute 57 | { 58 | } 59 | 60 | public class Class1 61 | { 62 | readonly int c = 1; 63 | 64 | [IsPureExceptReadLocally] 65 | public int DoSomething => c; 66 | }"; 67 | 68 | Utilities.AssertPure(code); 69 | } 70 | 71 | [Test] 72 | public void PropertyGetterThatReadsALocalReadWriteFieldIsPureExceptReadLocally() 73 | { 74 | string code = @" 75 | using System; 76 | 77 | public class IsPureExceptReadLocallyAttribute : Attribute 78 | { 79 | } 80 | 81 | public class Class1 82 | { 83 | int c = 1; 84 | 85 | [IsPureExceptReadLocally] 86 | public int DoSomething => c; 87 | }"; 88 | 89 | Utilities.AssertPure(code); 90 | } 91 | 92 | [Test] 93 | public void PropertyGetterThatWritesALocalReadWriteFieldIsNotPureExceptReadLocally() 94 | { 95 | string code = @" 96 | using System; 97 | 98 | public class IsPureExceptReadLocallyAttribute : Attribute 99 | { 100 | } 101 | 102 | public class Class1 103 | { 104 | int c = 1; 105 | 106 | [IsPureExceptReadLocally] 107 | public int DoSomething 108 | { 109 | get 110 | { 111 | c = 2; 112 | return 1; 113 | } 114 | } 115 | }"; 116 | 117 | Utilities.AssertImpure(code); 118 | } 119 | 120 | [Test] 121 | public void TestIsPureExceptReadLocallyAttributeOnInstanceAutomaticReadWriteProperty() 122 | { 123 | string code = @" 124 | using System; 125 | 126 | public class IsPureExceptReadLocallyAttribute : Attribute 127 | { 128 | } 129 | 130 | public class Class1 131 | { 132 | [IsPureExceptReadLocallyAttribute] 133 | public int Prop1 {get; set;} 134 | }"; 135 | 136 | Utilities.AssertImpure(code); 137 | 138 | } 139 | 140 | 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/CastingAsItRelatesToObjectMethods/Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using NUnit.Framework; 8 | 9 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod.CastingAsItRelatesToObjectMethods 10 | { 11 | [TestFixture] 12 | public class Tests 13 | { 14 | [Test] 15 | public void TestCastingFromClassWithImpureToStringMethodToObject() 16 | { 17 | string code = @" 18 | using System; 19 | 20 | public class IsPureAttribute : Attribute 21 | { 22 | } 23 | 24 | public class Class1 25 | { 26 | public override string ToString() => Console.ReadLine(); 27 | } 28 | 29 | public static class Module1 30 | { 31 | [IsPure] 32 | public static void DoSomething() 33 | { 34 | object obj = new Class1(); 35 | } 36 | }"; 37 | 38 | var dignostics = Utilities.RunPurityAnalyzer(code); 39 | dignostics.Length.Should().Be(1); 40 | } 41 | 42 | [Test] 43 | public void TestCastingFromClassWithImpureToStringMethodToAnInterface() 44 | { 45 | string code = @" 46 | using System; 47 | 48 | public class IsPureAttribute : Attribute 49 | { 50 | } 51 | 52 | public interface IInterface 53 | { 54 | } 55 | 56 | public class Class1 : IInterface 57 | { 58 | public override string ToString() => Console.ReadLine(); 59 | } 60 | 61 | public static class Module1 62 | { 63 | [IsPure] 64 | public static void DoSomething() 65 | { 66 | IInterface obj = new Class1(); 67 | } 68 | }"; 69 | 70 | var dignostics = Utilities.RunPurityAnalyzer(code); 71 | dignostics.Length.Should().Be(1); 72 | } 73 | 74 | [Test] 75 | public void TestCastingFromClassWithImpureToStringMethodToBaseClassWithPureToString() 76 | { 77 | string code = @" 78 | using System; 79 | 80 | public class IsPureAttribute : Attribute 81 | { 82 | } 83 | 84 | public class Base 85 | { 86 | public override string ToString() => string.Empty; 87 | } 88 | 89 | public class Class1 : Base 90 | { 91 | public override string ToString() => Console.ReadLine(); 92 | } 93 | 94 | public static class Module1 95 | { 96 | [IsPure] 97 | public static void DoSomething() 98 | { 99 | Base obj = new Class1(); 100 | } 101 | }"; 102 | 103 | var dignostics = Utilities.RunPurityAnalyzer(code); 104 | dignostics.Length.Should().Be(1); 105 | } 106 | 107 | [Test] 108 | public void TestCastingFromClassWithImpureToStringMethodToBaseClassThatDoesNotOverrideToString() 109 | { 110 | string code = @" 111 | using System; 112 | 113 | public class IsPureAttribute : Attribute 114 | { 115 | } 116 | 117 | public class Base 118 | { 119 | 120 | } 121 | 122 | public class Class1 : Base 123 | { 124 | public override string ToString() => Console.ReadLine(); 125 | } 126 | 127 | public static class Module1 128 | { 129 | [IsPure] 130 | public static void DoSomething() 131 | { 132 | Base obj = new Class1(); 133 | } 134 | }"; 135 | 136 | var dignostics = Utilities.RunPurityAnalyzer(code); 137 | dignostics.Length.Should().Be(1); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests.CompiledCsharpLib/PurityAnalyzer.Tests.CompiledCsharpLib.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {415F2DDC-9AF0-409C-A124-14EE7BD3B91C} 8 | Library 9 | Properties 10 | PurityAnalyzer.Tests.CompiledCsharpLib 11 | PurityAnalyzer.Tests.CompiledCsharpLib 12 | v4.6.2 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/MethodCallingTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 5 | { 6 | [TestFixture] 7 | public class MethodCallingTests 8 | { 9 | [Test] 10 | public void MethodThatCallsAPureMethodIsPure() 11 | { 12 | string code = @" 13 | using System; 14 | 15 | public class IsPureAttribute : Attribute 16 | { 17 | } 18 | 19 | public static class Module1 20 | { 21 | [IsPure] 22 | public static string DoSomething() 23 | { 24 | return DoSomethingElsePure(); 25 | } 26 | 27 | private static string DoSomethingElsePure() 28 | { 29 | return """"; 30 | } 31 | 32 | }"; 33 | 34 | Utilities.AssertPure(code); 35 | 36 | } 37 | 38 | [Test] 39 | public void MethodThatCallsAnImpureMethodIsImpure() 40 | { 41 | string code = @" 42 | using System; 43 | 44 | public class IsPureAttribute : Attribute 45 | { 46 | } 47 | 48 | public static class Module1 49 | { 50 | [IsPure] 51 | public static string DoSomething() 52 | { 53 | return DoSomethingElseImpure(); 54 | } 55 | 56 | private static int state; 57 | 58 | private static string DoSomethingElseImpure() 59 | { 60 | return state.ToString(); 61 | } 62 | 63 | }"; 64 | 65 | Utilities.AssertImpure(code); 66 | 67 | } 68 | 69 | [Test] 70 | public void MethodThatCallsAPureLocalFunctionIsPure() 71 | { 72 | string code = @" 73 | using System; 74 | 75 | public class IsPureAttribute : Attribute 76 | { 77 | } 78 | 79 | public static class Module1 80 | { 81 | [IsPure] 82 | public static string DoSomething() 83 | { 84 | string DoSomethingElsePure() 85 | { 86 | return """"; 87 | } 88 | 89 | return DoSomethingElsePure(); 90 | } 91 | }"; 92 | 93 | Utilities.AssertPure(code); 94 | 95 | } 96 | 97 | [Test] 98 | public void MethodThatCallsAnImpureLocalFunctionIsImpure() 99 | { 100 | string code = @" 101 | using System; 102 | 103 | public class IsPureAttribute : Attribute 104 | { 105 | } 106 | 107 | public static class Module1 108 | { 109 | private static int state; 110 | 111 | [IsPure] 112 | public static string DoSomething() 113 | { 114 | string DoSomethingElseImpure() 115 | { 116 | return state.ToString(); 117 | } 118 | 119 | return DoSomethingElseImpure(); 120 | } 121 | }"; 122 | 123 | Utilities.AssertImpure(code); 124 | 125 | } 126 | 127 | [Test] 128 | public void MethodThatCallsAnLocalFunctionThatUpdatesLocalStateIsPure() 129 | { 130 | string code = @" 131 | using System; 132 | 133 | public class IsPureAttribute : Attribute 134 | { 135 | } 136 | 137 | public static class Module1 138 | { 139 | [IsPure] 140 | public static int DoSomething() 141 | { 142 | int localstate = 0; 143 | 144 | int DoSomethingElseImpure() 145 | { 146 | localstate++; 147 | return localstate; 148 | } 149 | 150 | return DoSomethingElseImpure(); 151 | } 152 | }"; 153 | 154 | Utilities.AssertPure(code); 155 | 156 | } 157 | 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /PurityAnalyzer/ExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CSharp; 6 | using Microsoft.CodeAnalysis.CSharp.Syntax; 7 | 8 | namespace PurityAnalyzer 9 | { 10 | [CreateMatchMethods(typeof(IdentifierUsage))] 11 | public static class ExtensionMethods 12 | { 13 | 14 | 15 | public static TResult Match(this IdentifierUsage instance, Func readFromAndWrittenToCaseCase, Func writtenToCaseCase, Func readFromCaseCase) 16 | { 17 | if (instance is IdentifierUsage.ReadFromAndWrittenToCase readFromAndWrittenToCase) 18 | return readFromAndWrittenToCaseCase(readFromAndWrittenToCase); 19 | if (instance is IdentifierUsage.WrittenToCase writtenToCase) 20 | return writtenToCaseCase(writtenToCase); 21 | if (instance is IdentifierUsage.ReadFromCase readFromCase) 22 | return readFromCaseCase(readFromCase); 23 | throw new Exception("Invalid IdentifierUsage type"); 24 | } 25 | 26 | public static void Match(this IdentifierUsage instance, Action readFromAndWrittenToCaseCase, Action writtenToCaseCase, Action readFromCaseCase) 27 | { 28 | if (instance is IdentifierUsage.ReadFromAndWrittenToCase readFromAndWrittenToCase) 29 | { 30 | readFromAndWrittenToCaseCase(readFromAndWrittenToCase); 31 | return; 32 | } 33 | 34 | if (instance is IdentifierUsage.WrittenToCase writtenToCase) 35 | { 36 | writtenToCaseCase(writtenToCase); 37 | return; 38 | } 39 | 40 | if (instance is IdentifierUsage.ReadFromCase readFromCase) 41 | { 42 | readFromCaseCase(readFromCase); 43 | return; 44 | } 45 | 46 | throw new Exception("Invalid IdentifierUsage type"); 47 | } 48 | 49 | public static bool IsCompiled(this ISymbol symbol) => !symbol.IsInCode(); 50 | 51 | public static bool IsInCode(this ISymbol symbol) 52 | { 53 | return symbol.Locations.Length > 0 && symbol.Locations.All(x => x.IsInSource); 54 | } 55 | 56 | public static bool IsStatic(this BaseMethodDeclarationSyntax method) 57 | { 58 | return method.Modifiers.Any(SyntaxKind.StaticKeyword); 59 | } 60 | 61 | public static bool IsStatic(this PropertyDeclarationSyntax prop) 62 | { 63 | return prop.Modifiers.Any(SyntaxKind.StaticKeyword); 64 | } 65 | 66 | public static bool IsStatic(this FieldDeclarationSyntax field) 67 | { 68 | return field.Modifiers.Any(SyntaxKind.StaticKeyword); 69 | } 70 | 71 | public static T ThrowIf(this T value, Func condition, string errorMessage) 72 | { 73 | if(condition(value)) 74 | throw new Exception(errorMessage); 75 | 76 | return value; 77 | } 78 | 79 | public static IEnumerable GetItemsWithValues(this IEnumerable> enumerable) 80 | { 81 | foreach(var item in enumerable) 82 | if (item.HasValue) 83 | yield return item.GetValue(); 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnClass/IsPureAttributeOnClassTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnClass 5 | { 6 | [TestFixture] 7 | public class IsPureAttributeOnClassTests 8 | { 9 | [Test] 10 | public void IsPureOnClassRequiresStaticMethodsToBePure() 11 | { 12 | string code = @" 13 | using System; 14 | 15 | public class IsPureAttribute : Attribute 16 | { 17 | } 18 | 19 | [IsPure] 20 | public class Class1 21 | { 22 | static int c = 1; 23 | 24 | public static string DoSomething() 25 | { 26 | return c.ToString(); 27 | } 28 | }"; 29 | 30 | Utilities.AssertImpure(code); 31 | 32 | } 33 | 34 | [Test] 35 | public void IsPureOnClassRequiresInstanceMethodsToBePure() 36 | { 37 | string code = @" 38 | using System; 39 | 40 | public class IsPureAttribute : Attribute 41 | { 42 | } 43 | 44 | [IsPure] 45 | public class Class1 46 | { 47 | int c = 1; 48 | 49 | public string DoSomething() 50 | { 51 | return c.ToString(); 52 | } 53 | }"; 54 | 55 | Utilities.AssertImpure(code); 56 | 57 | } 58 | 59 | [Test] 60 | public void IsPureOnClassRequiresStaticPropertiesToBePure() 61 | { 62 | string code = @" 63 | using System; 64 | 65 | public class IsPureAttribute : Attribute 66 | { 67 | } 68 | 69 | [IsPure] 70 | public class Class1 71 | { 72 | static int a; 73 | 74 | 75 | public static int Prop1 76 | { 77 | get 78 | { 79 | return a++; 80 | } 81 | } 82 | }"; 83 | 84 | Utilities.AssertImpure(code); 85 | 86 | } 87 | 88 | [Test] 89 | public void IsPureOnClassRequiresInstancePropertiesToBePure() 90 | { 91 | string code = @" 92 | using System; 93 | 94 | public class IsPureAttribute : Attribute 95 | { 96 | } 97 | 98 | [IsPure] 99 | public class Class1 100 | { 101 | int a; 102 | 103 | public int Prop1 104 | { 105 | get 106 | { 107 | return a++; 108 | } 109 | } 110 | }"; 111 | 112 | Utilities.AssertImpure(code); 113 | 114 | } 115 | 116 | [Test] 117 | public void IsPureOnClassRequiresStaticConstructorToBePure() 118 | { 119 | string code = @" 120 | using System; 121 | 122 | public class IsPureAttribute : Attribute 123 | { 124 | } 125 | 126 | [IsPure] 127 | public class Class1 128 | { 129 | static Class1() => AnotherClass.state = 1; 130 | } 131 | 132 | public static class AnotherClass 133 | { 134 | public static int state; 135 | } 136 | "; 137 | 138 | Utilities.AssertImpure(code); 139 | 140 | } 141 | 142 | [Test] 143 | public void IsPureOnClassRequiresInstanceConstructorToBePure() 144 | { 145 | string code = @" 146 | using System; 147 | 148 | public class IsPureAttribute : Attribute 149 | { 150 | } 151 | 152 | [IsPure] 153 | public class Class1 154 | { 155 | public Class1() => AnotherClass.state = 1; 156 | } 157 | 158 | public static class AnotherClass 159 | { 160 | public static int state; 161 | } 162 | "; 163 | 164 | Utilities.AssertImpure(code); 165 | 166 | } 167 | 168 | 169 | [Test] 170 | public void CaseWhereMembersArePure() 171 | { 172 | string code = @" 173 | using System; 174 | 175 | public class IsPureAttribute : Attribute 176 | { 177 | } 178 | 179 | [IsPure] 180 | public class Class1 181 | { 182 | 183 | public int Prop1 184 | { 185 | get 186 | { 187 | return 1; 188 | } 189 | } 190 | 191 | public int DoSomething(int a) 192 | { 193 | return a + 1; 194 | } 195 | }"; 196 | 197 | Utilities.AssertPure(code); 198 | 199 | } 200 | 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/TestsRelatingToUsingOrNotUsingTAsObject.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using FluentAssertions; 3 | using NUnit.Framework; 4 | 5 | namespace PurityAnalyzer.Tests 6 | { 7 | [TestFixture] 8 | public class TestsRelatingToUsingOrNotUsingTAsObject 9 | { 10 | private static readonly string ClassWithImpureToStringEqualsAndGetHashCode = $@" 11 | public class ClassWithImpureToStringEqualsAndGetHashCode 12 | {{ 13 | public int state; 14 | 15 | public override string ToString() 16 | {{ 17 | state++; 18 | return """"; 19 | }} 20 | 21 | public override bool Equals(object obj) 22 | {{ 23 | state++; 24 | return false; 25 | }} 26 | 27 | public override int GetHashCode() 28 | {{ 29 | state++; 30 | return 0; 31 | }} 32 | }}"; 33 | 34 | [Test] 35 | public void CallingImmutableArrayCreateWhereTIsClassThatHasImpureToStringEqualsAndGetHashCode_LeavesMethodPure() 36 | { 37 | string code = $@" 38 | using System; 39 | using System.Collections.Immutable; 40 | 41 | public class IsPureAttribute : Attribute 42 | {{ 43 | }} 44 | 45 | {ClassWithImpureToStringEqualsAndGetHashCode} 46 | 47 | public static class Module1 48 | {{ 49 | [IsPure] 50 | public static void DoSomething() 51 | {{ 52 | var array = ImmutableArray.Create(new ClassWithImpureToStringEqualsAndGetHashCode()); 53 | }} 54 | }}"; 55 | 56 | var dignostics = Utilities.RunPurityAnalyzerWithImmutableCollections(code); 57 | dignostics.Length.Should().Be(0); 58 | 59 | } 60 | 61 | [TestCase("bool value = array.All(x => true);")] 62 | [TestCase("bool value = array.Any();")] 63 | [TestCase("bool value = array.Any(x => true);")] 64 | [TestCase("var value = array.SelectMany(x => new []{1,2,3});")] 65 | [TestCase("var value = array.SelectMany(x => new []{1,2,3}, (x,y) => 2);")] 66 | [TestCase("var value = array.Where(x => true);")] 67 | [TestCase("var value = array.Aggregate((x,y) => x);")] 68 | [TestCase("var value = array.Aggregate(1, (x,y) => x);")] 69 | [TestCase("var value = array.Aggregate(1, (x,y) => x, x => 2);")] 70 | [TestCase("var value = array.ElementAt(0);")] 71 | [TestCase("var value = array.ElementAtOrDefault(0);")] 72 | [TestCase("var value = array.First();")] 73 | [TestCase("var value = array.First(x => true);")] 74 | [TestCase("var value = array.FirstOrDefault();")] 75 | [TestCase("var value = array.FirstOrDefault(x => true);")] 76 | [TestCase("var value = array.Last();")] 77 | [TestCase("var value = array.Last(x => true);")] 78 | [TestCase("var value = array.LastOrDefault();")] 79 | [TestCase("var value = array.LastOrDefault(x => true);")] 80 | [TestCase("var value = array.Single();")] 81 | [TestCase("var value = array.Single(x => true);")] 82 | [TestCase("var value = array.SingleOrDefault();")] 83 | [TestCase("var value = array.SingleOrDefault(x => true);")] 84 | [TestCase("var value = array.ToArray();")] 85 | public void 86 | CallOnImmutableArrayOfClassThatHasImpureToStringEqualsAndGetHashCode_LeavesMethodPure(string code1) 87 | { 88 | string code = $@" 89 | using System; 90 | using System.Collections.Immutable; 91 | using System.Linq; 92 | 93 | public class IsPureAttribute : Attribute 94 | {{ 95 | }} 96 | 97 | {ClassWithImpureToStringEqualsAndGetHashCode} 98 | 99 | public static class Module1 100 | {{ 101 | [IsPure] 102 | public static void DoSomething() 103 | {{ 104 | var array = ImmutableArray.Create(new ClassWithImpureToStringEqualsAndGetHashCode()); 105 | 106 | {code1} 107 | }} 108 | }}"; 109 | 110 | var dignostics = Utilities.RunPurityAnalyzerWithImmutableCollections(code); 111 | dignostics.Length.Should().Be(0); 112 | } 113 | 114 | } 115 | } -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/FieldsOnInputParameterTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 5 | { 6 | [TestFixture] 7 | public class FieldsOnInputParameterTests 8 | { 9 | [Test] 10 | public void MethodThatReadsAReadOnlyFieldOnParameterWhoseTypeIsDefinedInCodeIsPure() 11 | { 12 | string code = @" 13 | using System; 14 | 15 | public class IsPureAttribute : Attribute 16 | { 17 | } 18 | 19 | public class Dto1 20 | { 21 | public readonly int Field1 = 5; 22 | } 23 | 24 | public static class Module1 25 | { 26 | [IsPure] 27 | public static int DoSomething(Dto1 input) 28 | { 29 | 30 | return input.Field1; 31 | } 32 | }"; 33 | 34 | Utilities.AssertPure(code); 35 | } 36 | 37 | //Even if input is mutable, reading such mutable input is a pure operation 38 | [Test] 39 | public void MethodThatReadsAReadWriteFieldOnParameterWhoseTypeIsDefinedInCodeIsPure() 40 | { 41 | string code = @" 42 | using System; 43 | 44 | public class IsPureAttribute : Attribute 45 | { 46 | } 47 | 48 | public class Dto1 49 | { 50 | public int Field1 = 5; 51 | } 52 | 53 | public static class Module1 54 | { 55 | [IsPure] 56 | public static int DoSomething(Dto1 input) 57 | { 58 | 59 | return input.Field1; 60 | } 61 | }"; 62 | 63 | Utilities.AssertPure(code); 64 | } 65 | 66 | [Test] 67 | public void MethodThatWritesAReadWriteFieldOnParameterWhoseTypeIsDefinedInCodeIsImpure() 68 | { 69 | string code = @" 70 | using System; 71 | 72 | public class IsPureAttribute : Attribute 73 | { 74 | } 75 | 76 | public class Dto1 77 | { 78 | public int Field1 = 5; 79 | } 80 | 81 | public static class Module1 82 | { 83 | [IsPure] 84 | public static string DoSomething(Dto1 input) 85 | { 86 | input.Field1 = 6; 87 | return """"; 88 | } 89 | }"; 90 | 91 | Utilities.AssertImpure(code); 92 | } 93 | 94 | [Test] 95 | public void MethodThatIncrementsAReadWriteFieldOnParameterWhoseTypeIsDefinedInCodeIsImpure() 96 | { 97 | string code = @" 98 | using System; 99 | 100 | public class IsPureAttribute : Attribute 101 | { 102 | } 103 | 104 | public class Dto1 105 | { 106 | public int Field1 = 5; 107 | } 108 | 109 | public static class Module1 110 | { 111 | [IsPure] 112 | public static string DoSomething(Dto1 input) 113 | { 114 | input.Field1 = input.Field1 + 1; 115 | return """"; 116 | } 117 | }"; 118 | 119 | Utilities.AssertImpure(code); 120 | } 121 | 122 | [Test] 123 | public void MethodThatIncrementsViaPlusPlusAReadWriteFieldOnParameterWhoseTypeIsDefinedInCodeIsImpure() 124 | { 125 | string code = @" 126 | using System; 127 | 128 | public class IsPureAttribute : Attribute 129 | { 130 | } 131 | 132 | public class Dto1 133 | { 134 | public int Field1 = 5; 135 | } 136 | 137 | public static class Module1 138 | { 139 | [IsPure] 140 | public static string DoSomething(Dto1 input) 141 | { 142 | input.Field1++; 143 | return """"; 144 | } 145 | }"; 146 | 147 | Utilities.AssertImpure(code); 148 | } 149 | 150 | [Test] 151 | public void MethodThatIncrementsViaPlusEqualsAReadWriteFieldOnParameterWhoseTypeIsDefinedInCodeIsImpure() 152 | { 153 | string code = @" 154 | using System; 155 | 156 | public class IsPureAttribute : Attribute 157 | { 158 | } 159 | 160 | public class Dto1 161 | { 162 | public int Field1 = 5; 163 | } 164 | 165 | public static class Module1 166 | { 167 | [IsPure] 168 | public static string DoSomething(Dto1 input) 169 | { 170 | input.Field1 += 1; 171 | return """"; 172 | } 173 | }"; 174 | 175 | Utilities.AssertImpure(code); 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/CompiledPropertiesOnInputParameterTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 5 | { 6 | [TestFixture] 7 | public class CompiledPropertiesOnInputParameterTests 8 | { 9 | [Test] 10 | public void MethodThatReadsAnAutomaticReadOnlyPropertyThatDoesNotHaveTheIsPureAttributeOnParameterWhoseTypeIsCompiledIsImpure() 11 | { 12 | string code = @" 13 | using System; 14 | using PurityAnalyzer.Tests.CompiledCsharpLib; 15 | 16 | public class IsPureAttribute : Attribute 17 | { 18 | } 19 | 20 | public static class Module1 21 | { 22 | [IsPure] 23 | public static string DoSomething(ImmutableDto1 input) 24 | { 25 | return input.Prop1.ToString(); 26 | } 27 | }"; 28 | 29 | Utilities.AssertImpure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 30 | } 31 | 32 | [Test] 33 | public void MethodThatReadsAnAutomaticReadOnlyPropertyThatHasTheIsPureAttributeOnParameterWhoseTypeIsCompiledIsPure() 34 | { 35 | string code = @" 36 | using System; 37 | using PurityAnalyzer.Tests.CompiledCsharpLib; 38 | 39 | public class IsPureAttribute : Attribute 40 | { 41 | } 42 | 43 | public static class Module1 44 | { 45 | [IsPure] 46 | public static int DoSomething(ImmutableDto1WithIsPureAttributeOnProperty input) 47 | { 48 | return input.Prop1; 49 | } 50 | }"; 51 | 52 | Utilities.AssertPure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 53 | } 54 | 55 | [Test] 56 | public void MethodThatReadsAnAutomaticReadOnlyPropertyWhoseClassHasTheIsPureAttributeOnParameterWhoseTypeIsCompiledIsPure() 57 | { 58 | string code = @" 59 | using System; 60 | using PurityAnalyzer.Tests.CompiledCsharpLib; 61 | 62 | public class IsPureAttribute : Attribute 63 | { 64 | } 65 | 66 | public static class Module1 67 | { 68 | [IsPure] 69 | public static int DoSomething(ImmutableDto1WithIsPureAttributeOnClass input) 70 | { 71 | return input.Prop1; 72 | } 73 | }"; 74 | 75 | Utilities.AssertPure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 76 | } 77 | 78 | 79 | [Test] 80 | public void MethodThatReadsAnAutomaticReadWritePropertyOnParameterWhoseTypeIsCompiledIsImpure() 81 | { 82 | string code = @" 83 | using System; 84 | using PurityAnalyzer.Tests.CompiledCsharpLib; 85 | 86 | public class IsPureAttribute : Attribute 87 | { 88 | } 89 | 90 | public static class Module1 91 | { 92 | [IsPure] 93 | public static string DoSomething(MutableDto1 input) 94 | { 95 | return input.Prop1.ToString(); 96 | } 97 | }"; 98 | 99 | Utilities.AssertImpure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 100 | } 101 | 102 | [Test] 103 | public void MethodThatWritesAnAutomaticReadWritePropertyOnParameterWhoseTypeIsCompiledIsImpure() 104 | { 105 | string code = @" 106 | using System; 107 | using PurityAnalyzer.Tests.CompiledCsharpLib; 108 | 109 | public class IsPureAttribute : Attribute 110 | { 111 | } 112 | 113 | public static class Module1 114 | { 115 | [IsPure] 116 | public static string DoSomething(MutableDto1 input) 117 | { 118 | input.Prop1 = 1; 119 | return """"; 120 | } 121 | }"; 122 | 123 | Utilities.AssertImpure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 124 | } 125 | 126 | [Test] 127 | public void MethodThatReadsAndWritesAnAutomaticReadWritePropertyOnParameterWhoseTypeIsCompiledIsImpure() 128 | { 129 | string code = @" 130 | using System; 131 | using PurityAnalyzer.Tests.CompiledCsharpLib; 132 | 133 | public class IsPureAttribute : Attribute 134 | { 135 | } 136 | 137 | public static class Module1 138 | { 139 | [IsPure] 140 | public static string DoSomething(MutableDto1 input) 141 | { 142 | input.Prop1 = input.Prop1 + 1; 143 | return """"; 144 | } 145 | }"; 146 | 147 | Utilities.AssertImpure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/AssumeIsPureAttributeTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 5 | { 6 | [TestFixture] 7 | public class AssumeIsPureAttributeTests 8 | { 9 | [Test] 10 | public void MethodThatCallsAnImpureMethodThatHasTheAssumeIsPureAttributeIsPure() 11 | { 12 | string code = @" 13 | using System; 14 | 15 | public class IsPureAttribute : Attribute 16 | { 17 | } 18 | 19 | public class AssumeIsPureAttribute : Attribute 20 | { 21 | } 22 | 23 | public static class Module1 24 | { 25 | [IsPure] 26 | public static string DoSomething() 27 | { 28 | return DoSomethingElseImpure(); 29 | } 30 | 31 | private static int state; 32 | 33 | [AssumeIsPure] 34 | private static string DoSomethingElseImpure() 35 | { 36 | return state.ToString(); 37 | } 38 | 39 | }"; 40 | 41 | Utilities.AssertPure(code); 42 | 43 | } 44 | 45 | 46 | [Test] 47 | public void MethodThatCallsAnImpureMethodInWhichTypeHasTheAssumeIsPureAttributeIsPure() 48 | { 49 | string code = @" 50 | using System; 51 | 52 | public class IsPureAttribute : Attribute 53 | { 54 | } 55 | 56 | public class AssumeIsPureAttribute : Attribute 57 | { 58 | } 59 | 60 | public static class Module1 61 | { 62 | [IsPure] 63 | public static string DoSomething() 64 | { 65 | return AnotherModule.DoSomethingElseImpure(); 66 | } 67 | } 68 | 69 | [AssumeIsPure] 70 | public static class AnotherModule 71 | { 72 | private static int state; 73 | 74 | public static string DoSomethingElseImpure() 75 | { 76 | return state.ToString(); 77 | } 78 | } 79 | "; 80 | 81 | Utilities.AssertPure(code); 82 | 83 | } 84 | 85 | [Test] 86 | public void CallingPureHigherOrderFunctionWithAnImpureFunctionClassMarkedWithAssumeIsPureAttributeKeepsMethodPure() 87 | { 88 | string code = @" 89 | using System; 90 | 91 | public class IsPureAttribute : Attribute 92 | { 93 | } 94 | 95 | public class AssumeIsPureAttribute : Attribute 96 | { 97 | } 98 | 99 | public interface IInterface 100 | { 101 | string Call(int input); 102 | } 103 | 104 | [AssumeIsPure] 105 | public class ImpureClass : IInterface 106 | { 107 | int state = 0; 108 | 109 | public string Call(int input) 110 | { 111 | state++; 112 | 113 | return input.ToString(); 114 | } 115 | } 116 | 117 | public static class Module1 118 | { 119 | [IsPure] 120 | public static string DoSomething() 121 | { 122 | return HigherOrderFunction(new ImpureClass()); 123 | } 124 | 125 | [IsPure] 126 | public static string HigherOrderFunction(IInterface function) 127 | { 128 | return function.Call(1); 129 | } 130 | }"; 131 | Utilities.AssertPure(code); 132 | } 133 | 134 | 135 | [Test] 136 | public void MethodThatCallsConstructorThatHasTheAssumeIsPureAttributeAndThatUpdatesStaticStateIsPure() 137 | { 138 | string code = @" 139 | using System; 140 | 141 | public class IsPureAttribute : Attribute 142 | { 143 | } 144 | 145 | public class AssumeIsPureAttribute : Attribute 146 | { 147 | } 148 | 149 | public class Test2 150 | { 151 | static int z; 152 | 153 | [AssumeIsPure] 154 | public Test2() 155 | { 156 | z++; 157 | } 158 | } 159 | 160 | public static class Module1 161 | { 162 | [IsPure] 163 | public static string DoSomething() 164 | { 165 | var x = new Test2(); 166 | 167 | return """"; 168 | } 169 | }"; 170 | 171 | Utilities.AssertPure(code); 172 | 173 | } 174 | 175 | [Test] 176 | public void MethodThatCallsConstructorThatHasTheAssumeIsPureAttributeAndThatUpdatesInstanceStateIsPure() 177 | { 178 | string code = @" 179 | using System; 180 | 181 | public class IsPureAttribute : Attribute 182 | { 183 | } 184 | 185 | public class AssumeIsPureAttribute : Attribute 186 | { 187 | } 188 | 189 | public class Test2 190 | { 191 | int z = 0; 192 | 193 | [AssumeIsPure] 194 | public Test2() 195 | { 196 | z++; 197 | } 198 | } 199 | 200 | public static class Module1 201 | { 202 | [IsPure] 203 | public static string DoSomething() 204 | { 205 | var x = new Test2(); 206 | 207 | return """"; 208 | } 209 | }"; 210 | 211 | Utilities.AssertPure(code); 212 | 213 | } 214 | 215 | 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/CustomBinaryOperatorsWhereSecondOperandIsNumberTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using NUnit.Framework; 8 | 9 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 10 | { 11 | [TestFixture] 12 | public class CustomBinaryOperatorsWhereSecondOperandIsNumberTests 13 | { 14 | [TestCaseSource(nameof(GetCases))] 15 | public void PureCustomBinaryOperatorMethodIsConsideredPure(string op) 16 | { 17 | string code = @" 18 | using System; 19 | 20 | public class IsPureAttribute : Attribute 21 | { 22 | } 23 | 24 | public class CustomType 25 | { 26 | [IsPure] 27 | public static CustomType operator " + op + @"(CustomType c1, int c2) 28 | { 29 | return new CustomType(); 30 | } 31 | } 32 | "; 33 | 34 | Utilities.AssertPure(code); 35 | 36 | } 37 | 38 | [TestCaseSource(nameof(GetCases))] 39 | public void ImpureCustomBinaryOperatorMethodIsConsideredImpure(string op) 40 | { 41 | string code = $@" 42 | using System; 43 | 44 | public class IsPureAttribute : Attribute 45 | {{ 46 | }} 47 | 48 | public class CustomType 49 | {{ 50 | static int state = 0; 51 | 52 | [IsPure] 53 | public static CustomType operator {op}(CustomType c1, int c2) 54 | {{ 55 | state++; 56 | return new CustomType(); 57 | }} 58 | }} 59 | "; 60 | 61 | Utilities.AssertImpure(code); 62 | 63 | } 64 | 65 | [TestCaseSource(nameof(GetCases))] 66 | public void MethodThatUsesPureCustomBinaryOperatorIsPure(string op) 67 | { 68 | string code = $@" 69 | using System; 70 | 71 | public class IsPureAttribute : Attribute 72 | {{ 73 | }} 74 | 75 | public class MyClass 76 | {{ 77 | [IsPure] 78 | public static CustomType DoSomething() 79 | {{ 80 | return new CustomType() {op} 1; 81 | }} 82 | }} 83 | 84 | public class CustomType 85 | {{ 86 | public static CustomType operator {op}(CustomType c1, int c2) 87 | {{ 88 | return new CustomType(); 89 | }} 90 | }} 91 | "; 92 | 93 | Utilities.AssertPure(code); 94 | 95 | } 96 | 97 | [TestCaseSource(nameof(GetCases))] 98 | public void MethodThatUsesImpureCustomBinaryOperatorIsImpure(string op) 99 | { 100 | string code = $@" 101 | using System; 102 | 103 | public class IsPureAttribute : Attribute 104 | {{ 105 | }} 106 | 107 | public class MyClass 108 | {{ 109 | [IsPure] 110 | public static CustomType DoSomething() 111 | {{ 112 | return new CustomType() {op} 1; 113 | }} 114 | }} 115 | 116 | public class CustomType 117 | {{ 118 | static int state = 0; 119 | 120 | public static CustomType operator {op}(CustomType c1, int c2) 121 | {{ 122 | state++; 123 | return new CustomType(); 124 | }} 125 | }} 126 | "; 127 | 128 | Utilities.AssertImpure(code); 129 | 130 | } 131 | 132 | [TestCaseSource(nameof(GetCases))] 133 | public void MethodThatUsesPureCustomBinaryOperatorViaOperatorEqualsIsPure(string op) 134 | { 135 | string code = $@" 136 | using System; 137 | 138 | public class IsPureAttribute : Attribute 139 | {{ 140 | }} 141 | 142 | public class MyClass 143 | {{ 144 | [IsPure] 145 | public static CustomType DoSomething() 146 | {{ 147 | var a = new CustomType(); 148 | 149 | a {op}= 1; 150 | 151 | return a; 152 | }} 153 | }} 154 | 155 | public class CustomType 156 | {{ 157 | public static CustomType operator {op}(CustomType c1, int c2) 158 | {{ 159 | return new CustomType(); 160 | }} 161 | }} 162 | "; 163 | 164 | Utilities.AssertPure(code); 165 | 166 | } 167 | 168 | [TestCaseSource(nameof(GetCases))] 169 | public void MethodThatUsesImpureCustomBinaryOperatorViaOperatorEqualsIsImpure(string op) 170 | { 171 | string code = $@" 172 | using System; 173 | 174 | public class IsPureAttribute : Attribute 175 | {{ 176 | }} 177 | 178 | public class MyClass 179 | {{ 180 | [IsPure] 181 | public static CustomType DoSomething() 182 | {{ 183 | var a = new CustomType(); 184 | 185 | a {op}= 1; 186 | 187 | return a; 188 | }} 189 | }} 190 | 191 | public class CustomType 192 | {{ 193 | static int state = 0; 194 | 195 | public static CustomType operator {op}(CustomType c1, int c2) 196 | {{ 197 | state++; 198 | return new CustomType(); 199 | }} 200 | }} 201 | "; 202 | 203 | Utilities.AssertImpure(code); 204 | 205 | } 206 | 207 | private static IEnumerable GetCases() 208 | { 209 | yield return ">>"; 210 | yield return "<<"; 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/StaticFieldTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 5 | { 6 | [TestFixture] 7 | public class StaticFieldTests 8 | { 9 | 10 | [Test] 11 | public void MethodThatSimplyReturnsAnEmptyStringIsPure() 12 | { 13 | string code = @" 14 | using System; 15 | 16 | public class IsPureAttribute : Attribute 17 | { 18 | } 19 | 20 | public static class Module1 21 | { 22 | public static int state; 23 | [IsPure] 24 | public static string DoSomething() 25 | { 26 | return """"; 27 | } 28 | }"; 29 | 30 | Utilities.AssertPure(code); 31 | 32 | } 33 | 34 | [Test] 35 | public void MethodThatMutatesStaticFieldIsImpure() 36 | { 37 | string code = @" 38 | using System; 39 | 40 | public class IsPureAttribute : Attribute 41 | { 42 | } 43 | 44 | public static class Module1 45 | { 46 | public static int state; 47 | [IsPure] 48 | public static string DoSomething() 49 | { 50 | state = 6; 51 | return """"; 52 | } 53 | }"; 54 | 55 | Utilities.AssertImpure(code); 56 | } 57 | 58 | [Test] 59 | public void MethodThatReadsAndMutatesStaticFieldIsImpure() 60 | { 61 | string code = @" 62 | using System; 63 | 64 | public class IsPureAttribute : Attribute 65 | { 66 | } 67 | 68 | public static class Module1 69 | { 70 | public static int state; 71 | [IsPure] 72 | public static string DoSomething() 73 | { 74 | state = state + 6; 75 | return """"; 76 | } 77 | }"; 78 | 79 | Utilities.AssertImpure(code); 80 | } 81 | 82 | [Test] 83 | public void MethodThatIncrementsStaticFieldIsImpure() 84 | { 85 | string code = @" 86 | using System; 87 | 88 | public class IsPureAttribute : Attribute 89 | { 90 | } 91 | 92 | public static class Module1 93 | { 94 | public static int state; 95 | [IsPure] 96 | public static string DoSomething() 97 | { 98 | state++; 99 | return """"; 100 | } 101 | }"; 102 | 103 | Utilities.AssertImpure(code); 104 | } 105 | 106 | [Test] 107 | public void MethodThatIncrementsStaticFieldViaPlusEqualsIsImpure() 108 | { 109 | string code = @" 110 | using System; 111 | 112 | public class IsPureAttribute : Attribute 113 | { 114 | } 115 | 116 | public static class Module1 117 | { 118 | public static int state; 119 | [IsPure] 120 | public static string DoSomething() 121 | { 122 | state += 1; 123 | return """"; 124 | } 125 | }"; 126 | 127 | Utilities.AssertImpure(code); 128 | } 129 | 130 | [Test] 131 | public void MethodThatDecrementsStaticFieldIsImpure() 132 | { 133 | string code = @" 134 | using System; 135 | 136 | public class IsPureAttribute : Attribute 137 | { 138 | } 139 | 140 | public static class Module1 141 | { 142 | public static int state; 143 | [IsPure] 144 | public static string DoSomething() 145 | { 146 | state--; 147 | return """"; 148 | } 149 | }"; 150 | 151 | Utilities.AssertImpure(code); 152 | } 153 | 154 | [Test] 155 | public void MethodThatDecrementsStaticFieldViaMinusEqualsIsImpure() 156 | { 157 | string code = @" 158 | using System; 159 | 160 | public class IsPureAttribute : Attribute 161 | { 162 | } 163 | 164 | public static class Module1 165 | { 166 | public static int state; 167 | [IsPure] 168 | public static string DoSomething() 169 | { 170 | state -= 1; 171 | return """"; 172 | } 173 | }"; 174 | 175 | Utilities.AssertImpure(code); 176 | } 177 | 178 | 179 | [Test] 180 | public void MethodThatReadsNonReadOnlyStaticFieldIsImpure() 181 | { 182 | string code = @" 183 | using System; 184 | 185 | public class IsPureAttribute : Attribute 186 | { 187 | } 188 | 189 | public static class Module1 190 | { 191 | public static int state; 192 | [IsPure] 193 | public static string DoSomething() 194 | { 195 | return state.ToString(); 196 | } 197 | }"; 198 | Utilities.AssertImpure(code); 199 | } 200 | 201 | [Test] 202 | public void MethodThatReadsReadOnlyStaticFieldIsPure() 203 | { 204 | string code = @" 205 | using System; 206 | 207 | public class IsPureAttribute : Attribute 208 | { 209 | } 210 | 211 | public static class Module1 212 | { 213 | public static readonly int state = 1; 214 | [IsPure] 215 | public static int DoSomething() 216 | { 217 | 218 | return state; 219 | } 220 | }"; 221 | 222 | Utilities.AssertPure(code); 223 | } 224 | } 225 | 226 | } 227 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/CustomUnaryOperatorsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using NUnit.Framework; 8 | 9 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 10 | { 11 | [TestFixture] 12 | public class CustomUnaryOperatorsTests 13 | { 14 | [TestCaseSource(nameof(GetPrefixCases))] 15 | public void PureCustomUnaryOperatorMethodIsConsideredPure(string op) 16 | { 17 | string code = @" 18 | using System; 19 | 20 | public class IsPureAttribute : Attribute 21 | { 22 | } 23 | 24 | public class CustomType 25 | { 26 | [IsPure] 27 | public static CustomType operator " + op + @"(CustomType c1) 28 | { 29 | return new CustomType(); 30 | } 31 | } 32 | "; 33 | 34 | Utilities.AssertPure(code); 35 | 36 | } 37 | 38 | [TestCaseSource(nameof(GetPrefixCases))] 39 | public void ImpureCustomUnaryOperatorMethodIsConsideredImpure(string op) 40 | { 41 | string code = $@" 42 | using System; 43 | 44 | public class IsPureAttribute : Attribute 45 | {{ 46 | }} 47 | 48 | public class CustomType 49 | {{ 50 | static int state = 0; 51 | 52 | [IsPure] 53 | public static CustomType operator {op}(CustomType c1) 54 | {{ 55 | state++; 56 | return new CustomType(); 57 | }} 58 | }} 59 | "; 60 | 61 | Utilities.AssertImpure(code); 62 | 63 | } 64 | 65 | [TestCaseSource(nameof(GetPrefixCases))] 66 | public void MethodThatUsesPureCustomUnaryOperator_Prefix_IsPure(string op) 67 | { 68 | string code = $@" 69 | using System; 70 | 71 | public class IsPureAttribute : Attribute 72 | {{ 73 | }} 74 | 75 | public class MyClass 76 | {{ 77 | [IsPure] 78 | public static CustomType DoSomething() 79 | {{ 80 | var a = new CustomType(); 81 | return {op}a; 82 | }} 83 | }} 84 | 85 | public class CustomType 86 | {{ 87 | public static CustomType operator {op}(CustomType c1) 88 | {{ 89 | return new CustomType(); 90 | }} 91 | }} 92 | "; 93 | 94 | Utilities.AssertPure(code); 95 | 96 | } 97 | 98 | [TestCaseSource(nameof(GetPrefixCases))] 99 | public void MethodThatUsesImpureCustomUnaryOperator_Prefix_IsImpure(string op) 100 | { 101 | string code = $@" 102 | using System; 103 | 104 | public class IsPureAttribute : Attribute 105 | {{ 106 | }} 107 | 108 | public class MyClass 109 | {{ 110 | [IsPure] 111 | public static CustomType DoSomething() 112 | {{ 113 | var a = new CustomType(); 114 | return {op}a; 115 | }} 116 | }} 117 | 118 | public class CustomType 119 | {{ 120 | static int state = 0; 121 | 122 | public static CustomType operator {op}(CustomType c1) 123 | {{ 124 | state++; 125 | return new CustomType(); 126 | }} 127 | }} 128 | "; 129 | 130 | Utilities.AssertImpure(code); 131 | 132 | } 133 | 134 | [TestCaseSource(nameof(GetPostfixCases))] 135 | public void MethodThatUsesPureCustomUnaryOperator_Postfix_IsPure(string op) 136 | { 137 | string code = $@" 138 | using System; 139 | 140 | public class IsPureAttribute : Attribute 141 | {{ 142 | }} 143 | 144 | public class MyClass 145 | {{ 146 | [IsPure] 147 | public static CustomType DoSomething() 148 | {{ 149 | var a = new CustomType(); 150 | return a{op}; 151 | }} 152 | }} 153 | 154 | public class CustomType 155 | {{ 156 | public static CustomType operator {op}(CustomType c1) 157 | {{ 158 | return new CustomType(); 159 | }} 160 | }} 161 | "; 162 | 163 | Utilities.AssertPure(code); 164 | 165 | } 166 | 167 | [TestCaseSource(nameof(GetPostfixCases))] 168 | public void MethodThatUsesImpureCustomUnaryOperator_Postfix_IsImpure(string op) 169 | { 170 | string code = $@" 171 | using System; 172 | 173 | public class IsPureAttribute : Attribute 174 | {{ 175 | }} 176 | 177 | public class MyClass 178 | {{ 179 | [IsPure] 180 | public static CustomType DoSomething() 181 | {{ 182 | var a = new CustomType(); 183 | return a{op}; 184 | }} 185 | }} 186 | 187 | public class CustomType 188 | {{ 189 | static int state = 0; 190 | 191 | public static CustomType operator {op}(CustomType c1) 192 | {{ 193 | state++; 194 | return new CustomType(); 195 | }} 196 | }} 197 | "; 198 | 199 | Utilities.AssertImpure(code); 200 | 201 | } 202 | 203 | 204 | private static IEnumerable GetPrefixCases() 205 | { 206 | yield return "+"; 207 | yield return "-"; 208 | yield return "--"; 209 | yield return "++"; 210 | yield return "!"; 211 | yield return "~"; 212 | } 213 | 214 | private static IEnumerable GetPostfixCases() 215 | { 216 | yield return "--"; 217 | yield return "++"; 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/CustomBinaryOperatorsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using NUnit.Framework; 8 | 9 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 10 | { 11 | [TestFixture] 12 | public class CustomBinaryOperatorsTests 13 | { 14 | [TestCaseSource(nameof(GetCases))] 15 | public void PureCustomBinaryOperatorMethodIsConsideredPure(string op) 16 | { 17 | string code = @" 18 | using System; 19 | 20 | public class IsPureAttribute : Attribute 21 | { 22 | } 23 | 24 | public class CustomType 25 | { 26 | [IsPure] 27 | public static CustomType operator " + op + @"(CustomType c1, CustomType c2) 28 | { 29 | return new CustomType(); 30 | } 31 | } 32 | "; 33 | 34 | Utilities.AssertPure(code); 35 | 36 | } 37 | 38 | [TestCaseSource(nameof(GetCases))] 39 | public void ImpureCustomBinaryOperatorMethodIsConsideredImpure(string op) 40 | { 41 | string code = $@" 42 | using System; 43 | 44 | public class IsPureAttribute : Attribute 45 | {{ 46 | }} 47 | 48 | public class CustomType 49 | {{ 50 | static int state = 0; 51 | 52 | [IsPure] 53 | public static CustomType operator {op}(CustomType c1, CustomType c2) 54 | {{ 55 | state++; 56 | return new CustomType(); 57 | }} 58 | }} 59 | "; 60 | 61 | Utilities.AssertImpure(code); 62 | 63 | } 64 | 65 | [TestCaseSource(nameof(GetCases))] 66 | public void MethodThatUsesPureCustomBinaryOperatorIsPure(string op) 67 | { 68 | string code = $@" 69 | using System; 70 | 71 | public class IsPureAttribute : Attribute 72 | {{ 73 | }} 74 | 75 | public class MyClass 76 | {{ 77 | [IsPure] 78 | public static CustomType DoSomething() 79 | {{ 80 | return new CustomType() {op} new CustomType(); 81 | }} 82 | }} 83 | 84 | public class CustomType 85 | {{ 86 | public static CustomType operator {op}(CustomType c1, CustomType c2) 87 | {{ 88 | return new CustomType(); 89 | }} 90 | }} 91 | "; 92 | 93 | Utilities.AssertPure(code); 94 | 95 | } 96 | 97 | [TestCaseSource(nameof(GetCases))] 98 | public void MethodThatUsesImpureCustomBinaryOperatorIsImpure(string op) 99 | { 100 | string code = $@" 101 | using System; 102 | 103 | public class IsPureAttribute : Attribute 104 | {{ 105 | }} 106 | 107 | public class MyClass 108 | {{ 109 | [IsPure] 110 | public static CustomType DoSomething() 111 | {{ 112 | return new CustomType() {op} new CustomType(); 113 | }} 114 | }} 115 | 116 | public class CustomType 117 | {{ 118 | static int state = 0; 119 | 120 | public static CustomType operator {op}(CustomType c1, CustomType c2) 121 | {{ 122 | state++; 123 | return new CustomType(); 124 | }} 125 | }} 126 | "; 127 | 128 | Utilities.AssertImpure(code); 129 | 130 | } 131 | 132 | [TestCaseSource(nameof(GetCases))] 133 | public void MethodThatUsesPureCustomBinaryOperatorViaOperatorEqualsIsPure(string op) 134 | { 135 | string code = $@" 136 | using System; 137 | 138 | public class IsPureAttribute : Attribute 139 | {{ 140 | }} 141 | 142 | public class MyClass 143 | {{ 144 | [IsPure] 145 | public static CustomType DoSomething() 146 | {{ 147 | var a = new CustomType(); 148 | 149 | a {op}= new CustomType(); 150 | 151 | return a; 152 | }} 153 | }} 154 | 155 | public class CustomType 156 | {{ 157 | public static CustomType operator {op}(CustomType c1, CustomType c2) 158 | {{ 159 | return new CustomType(); 160 | }} 161 | }} 162 | "; 163 | 164 | Utilities.AssertPure(code); 165 | 166 | } 167 | 168 | [TestCaseSource(nameof(GetCases))] 169 | public void MethodThatUsesImpureCustomBinaryOperatorViaOperatorEqualsIsImpure(string op) 170 | { 171 | string code = $@" 172 | using System; 173 | 174 | public class IsPureAttribute : Attribute 175 | {{ 176 | }} 177 | 178 | public class MyClass 179 | {{ 180 | [IsPure] 181 | public static CustomType DoSomething() 182 | {{ 183 | var a = new CustomType(); 184 | 185 | a {op}= new CustomType(); 186 | 187 | return a; 188 | }} 189 | }} 190 | 191 | public class CustomType 192 | {{ 193 | static int state = 0; 194 | 195 | public static CustomType operator {op}(CustomType c1, CustomType c2) 196 | {{ 197 | state++; 198 | return new CustomType(); 199 | }} 200 | }} 201 | "; 202 | 203 | Utilities.AssertImpure(code); 204 | 205 | } 206 | 207 | private static IEnumerable GetCases() 208 | { 209 | yield return "+"; 210 | yield return "-"; 211 | yield return "*"; 212 | yield return "/"; 213 | yield return "%"; 214 | yield return "&"; 215 | yield return "^"; 216 | yield return "|"; 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/NotUsedAsObjectAttributeTests/DotNetMethodsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using NUnit.Framework; 8 | using NUnit.Framework.Internal; 9 | 10 | namespace PurityAnalyzer.Tests.NotUsedAsObjectAttributeTests 11 | { 12 | [TestFixture] 13 | public class DotNetMethodsTests 14 | { 15 | private string class1Code = @" 16 | public class Class1 17 | { 18 | static int state = 0; 19 | 20 | public override string ToString() 21 | { 22 | state++; 23 | return string.Empty; 24 | } 25 | }"; 26 | 27 | private string isPureAttributeCode = @" 28 | public class IsPureAttribute : Attribute 29 | { 30 | }"; 31 | 32 | [Test] 33 | public void CallingEnumerableWhereAndUsingTypeArgumentThatHasImpureToStringMethod_KeepsMethodPure() 34 | { 35 | string code = $@" 36 | using System; 37 | using System.Linq; 38 | 39 | {class1Code} 40 | {isPureAttributeCode} 41 | 42 | public static class Module1 43 | {{ 44 | [IsPure] 45 | public static void DoSomething() 46 | {{ 47 | var result = new []{{new Class1()}}.Where(x => true); 48 | }} 49 | }}"; 50 | Utilities.AssertPure(code); 51 | } 52 | 53 | [Test] 54 | public void CallingEnumerableSelectAndUsingTSourceAndTResultTypeArgumentsThatHaveImpureToStringMethod_KeepsMethodPure() 55 | { 56 | string code = $@" 57 | using System; 58 | using System.Linq; 59 | 60 | {class1Code} 61 | {isPureAttributeCode} 62 | 63 | public static class Module1 64 | {{ 65 | [IsPure] 66 | public static void DoSomething() 67 | {{ 68 | var result = new []{{new Class1()}}.Select(x => new Class1()); 69 | }} 70 | }}"; 71 | Utilities.AssertPure(code); 72 | } 73 | 74 | [Test] 75 | public void CallingEnumerableSelectManyAndUsingTSourceAndTResultTypeArgumentsThatHaveImpureToStringMethod_KeepsMethodPure() 76 | { 77 | string code = $@" 78 | using System; 79 | using System.Linq; 80 | 81 | {class1Code} 82 | {isPureAttributeCode} 83 | 84 | public static class Module1 85 | {{ 86 | [IsPure] 87 | public static void DoSomething() 88 | {{ 89 | var result = new []{{new Class1()}}.SelectMany(x => new[]{{new Class1()}}); 90 | }} 91 | }}"; 92 | Utilities.AssertPure(code); 93 | } 94 | 95 | [Test] 96 | public void CallingEnumerableFirstAndUsingTypeArgumentThatHasImpureToStringMethod_KeepsMethodPure() 97 | { 98 | string code = $@" 99 | using System; 100 | using System.Linq; 101 | 102 | {class1Code} 103 | {isPureAttributeCode} 104 | 105 | public static class Module1 106 | {{ 107 | [IsPure] 108 | public static void DoSomething() 109 | {{ 110 | var result = new []{{new Class1()}}.First(); 111 | }} 112 | }}"; 113 | Utilities.AssertPure(code); 114 | } 115 | 116 | [Test] 117 | public void CallingEnumerableGroupByAndUsingTSourceTypeArgumentThatHasImpureToStringMethod_KeepsMethodPure() 118 | { 119 | string code = $@" 120 | using System; 121 | using System.Linq; 122 | 123 | {class1Code} 124 | {isPureAttributeCode} 125 | 126 | public static class Module1 127 | {{ 128 | [IsPure] 129 | public static void DoSomething() 130 | {{ 131 | var result = new []{{new Class1()}}.GroupBy(x => 'a'); 132 | }} 133 | }}"; 134 | 135 | Utilities.AssertPure(code); 136 | } 137 | 138 | [Test] 139 | public void CallingEnumerableGroupByAndUsingTKeyTypeArgumentThatHasImpureToStringMethod_MakesMethodImpure() 140 | { 141 | string code = $@" 142 | using System; 143 | using System.Linq; 144 | 145 | {class1Code} 146 | {isPureAttributeCode} 147 | 148 | public static class Module1 149 | {{ 150 | [IsPure] 151 | public static void DoSomething() 152 | {{ 153 | var result = new []{{'a'}}.GroupBy(x => new Class1()); 154 | }} 155 | }}"; 156 | 157 | Utilities.AssertImpure(code); 158 | } 159 | 160 | [Test] 161 | public void CallingListConvertAllAndUsingTypeArgumentForTThatHasImpureToStringMethod_KeepsMethodPure() 162 | { 163 | string code = $@" 164 | using System; 165 | using System.Linq; 166 | using System.Collections.Generic; 167 | 168 | {class1Code} 169 | {isPureAttributeCode} 170 | 171 | public static class Module1 172 | {{ 173 | [IsPure] 174 | public static void DoSomething() 175 | {{ 176 | var result = new List{{new Class1()}}.ConvertAll(x => 'a'); 177 | }} 178 | }}"; 179 | Utilities.AssertPure(code); 180 | } 181 | 182 | [Test] 183 | public void CallingListConvertAllAndUsingTypeArgumentForTOutputThatHasImpureToStringMethod_KeepsMethodPure() 184 | { 185 | string code = $@" 186 | using System; 187 | using System.Linq; 188 | using System.Collections.Generic; 189 | 190 | {class1Code} 191 | {isPureAttributeCode} 192 | 193 | public static class Module1 194 | {{ 195 | [IsPure] 196 | public static void DoSomething() 197 | {{ 198 | var result = new List {{'c'}}.ConvertAll(x => new Class1()); 199 | }} 200 | }}"; 201 | Utilities.AssertPure(code); 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/DoesNotUseClassTypeParameterAsObjectAttributeTests/Tests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.DoesNotUseClassTypeParameterAttributeTests 5 | { 6 | [TestFixture] 7 | public class Tests 8 | { 9 | [Test] 10 | public void MethodThatDoesNothing_DoesNotUseClassTypeParameterTAsObject() 11 | { 12 | string code = @" 13 | using System; 14 | 15 | public class DoesNotUseClassTypeParameterAsObjectAttribute : Attribute 16 | { 17 | public string parameterName; 18 | 19 | public DoesNotUseClassTypeParameterAsObjectAttribute(string parameterName) => this.parameterName = parameterName; 20 | } 21 | 22 | public class Class1 23 | { 24 | [DoesNotUseClassTypeParameterAsObjectAttribute(nameof(T))] 25 | public void DoSomething(T input) 26 | { 27 | 28 | } 29 | }"; 30 | 31 | Utilities.AssertPure(code); 32 | 33 | } 34 | 35 | [Test] 36 | public void MethodThatCallsToStringOnT_UsesClassTypeParameterTAsObject() 37 | { 38 | string code = @" 39 | using System; 40 | 41 | public class DoesNotUseClassTypeParameterAsObjectAttribute : Attribute 42 | { 43 | public string parameterName; 44 | 45 | public DoesNotUseClassTypeParameterAsObjectAttribute(string parameterName) => this.parameterName = parameterName; 46 | } 47 | 48 | public class Class1 49 | { 50 | [DoesNotUseClassTypeParameterAsObjectAttribute(nameof(T))] 51 | public void DoSomething(T input) 52 | { 53 | var result = input.ToString(); 54 | } 55 | }"; 56 | 57 | Utilities.AssertImpure(code); 58 | 59 | } 60 | 61 | 62 | [Test] 63 | public void GenericMethodThatCallsToStringOnMethodTypeParameterT_DoesNotUseClassTypeParameterTAsObject() 64 | { 65 | string code = @" 66 | using System; 67 | 68 | public class DoesNotUseClassTypeParameterAsObjectAttribute : Attribute 69 | { 70 | public string parameterName; 71 | 72 | public DoesNotUseClassTypeParameterAsObjectAttribute(string parameterName) => this.parameterName = parameterName; 73 | } 74 | 75 | public class Class1 76 | { 77 | [DoesNotUseClassTypeParameterAsObjectAttribute(nameof(T))] 78 | public void DoSomething(T input) 79 | { 80 | var result = input.ToString(); 81 | } 82 | }"; 83 | 84 | Utilities.AssertPure(code); 85 | 86 | } 87 | 88 | 89 | [Test] 90 | public void MethodThatCallsToStringOnTIndirectlyViaCallingAnotherMethod_UsesClassTypeParameterTAsObject() 91 | { 92 | string code = @" 93 | using System; 94 | 95 | public class DoesNotUseClassTypeParameterAsObjectAttribute : Attribute 96 | { 97 | public string parameterName; 98 | 99 | public DoesNotUseClassTypeParameterAsObjectAttribute(string parameterName) => this.parameterName = parameterName; 100 | } 101 | 102 | public class Class1 103 | { 104 | [DoesNotUseClassTypeParameterAsObjectAttribute(nameof(T))] 105 | public void DoSomething(T input) 106 | { 107 | DoSomething2(input); 108 | } 109 | 110 | public void DoSomething2(T input) 111 | { 112 | var result = input.ToString(); 113 | } 114 | }"; 115 | 116 | Utilities.AssertImpure(code); 117 | 118 | } 119 | 120 | [Test] 121 | public void MethodThatCallsAnotherMethodThatDoesNotUseClassTypeParameter_DoesNotUseClassTypeParameterTAsObject() 122 | { 123 | string code = @" 124 | using System; 125 | 126 | public class DoesNotUseClassTypeParameterAsObjectAttribute : Attribute 127 | { 128 | public string parameterName; 129 | 130 | public DoesNotUseClassTypeParameterAsObjectAttribute(string parameterName) => this.parameterName = parameterName; 131 | } 132 | 133 | public class Class1 134 | { 135 | [DoesNotUseClassTypeParameterAsObjectAttribute(nameof(T))] 136 | public void DoSomething(T input) 137 | { 138 | DoSomething2(input); 139 | } 140 | 141 | public void DoSomething2(T input) 142 | { 143 | 144 | } 145 | }"; 146 | 147 | Utilities.AssertPure(code); 148 | 149 | } 150 | 151 | [Test] 152 | public void DoesNotUseClassTypeParameterAsObjectAttributeAnalysisHandlesRecursionWithoutDeadlocking() 153 | { 154 | string code = @" 155 | using System; 156 | 157 | public class DoesNotUseClassTypeParameterAsObjectAttribute : Attribute 158 | { 159 | public string parameterName; 160 | 161 | public DoesNotUseClassTypeParameterAsObjectAttribute(string parameterName) => this.parameterName = parameterName; 162 | } 163 | 164 | public class Class1 165 | { 166 | [DoesNotUseClassTypeParameterAsObjectAttribute(nameof(T))] 167 | public void DoSomething(T input, int index) 168 | { 169 | if(index <= 0) 170 | return; 171 | DoSomething(input, index - 1); 172 | } 173 | }"; 174 | 175 | Utilities.AssertPure(code); 176 | 177 | } 178 | 179 | [Test] 180 | public void DoesNotUseClassTypeParameterAsObjectAttributeAnalysisHandlesRecursionWithoutDeadlocking_CaseWhereRecursionIsInMethodNotMarkedWithAttribute() 181 | { 182 | string code = @" 183 | using System; 184 | 185 | public class DoesNotUseClassTypeParameterAsObjectAttribute : Attribute 186 | { 187 | public string parameterName; 188 | 189 | public DoesNotUseClassTypeParameterAsObjectAttribute(string parameterName) => this.parameterName = parameterName; 190 | } 191 | 192 | public class Class1 193 | { 194 | [DoesNotUseClassTypeParameterAsObjectAttribute(nameof(T))] 195 | public void DoSomething(T input) 196 | { 197 | DoSomething2(input, 10); 198 | } 199 | 200 | public void DoSomething2(T input, int index) 201 | { 202 | if(index <= 0) 203 | return; 204 | DoSomething2(input, index - 1); 205 | } 206 | }"; 207 | 208 | Utilities.AssertPure(code); 209 | 210 | } 211 | 212 | } 213 | } 214 | 215 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/CustomComparisonOperatorsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using NUnit.Framework; 8 | 9 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 10 | { 11 | [TestFixture] 12 | public class CustomComparisonOperatorsTests 13 | { 14 | [Test] 15 | public void PureCustomEqualityComparisonOperatorMethodIsConsideredPure() 16 | { 17 | string code = @" 18 | using System; 19 | 20 | public class IsPureAttribute : Attribute 21 | { 22 | } 23 | 24 | public class CustomType 25 | { 26 | [IsPure] 27 | public static bool operator ==(CustomType c1, CustomType c2) 28 | { 29 | return true; 30 | } 31 | 32 | public static bool operator !=(CustomType c1, CustomType c2) 33 | { 34 | return true; 35 | } 36 | } 37 | "; 38 | 39 | Utilities.AssertPure(code); 40 | 41 | } 42 | 43 | [Test] 44 | public void ImpureCustomEqualityComparisonOperatorMethodIsConsideredImpure() 45 | { 46 | string code = @" 47 | using System; 48 | 49 | public class IsPureAttribute : Attribute 50 | { 51 | } 52 | 53 | public class CustomType 54 | { 55 | static int state = 0; 56 | 57 | [IsPure] 58 | public static bool operator ==(CustomType c1, CustomType c2) 59 | { 60 | state++; 61 | return true; 62 | } 63 | 64 | public static bool operator !=(CustomType c1, CustomType c2) 65 | { 66 | return true; 67 | } 68 | } 69 | "; 70 | 71 | Utilities.AssertImpure(code); 72 | 73 | } 74 | 75 | [Test] 76 | public void MethodThatUsesPureCustomEqualityComparisonOperatorIsPure() 77 | { 78 | string code = @" 79 | using System; 80 | 81 | public class IsPureAttribute : Attribute 82 | { 83 | } 84 | 85 | public class MyClass 86 | { 87 | [IsPure] 88 | public static bool DoSomething() 89 | { 90 | return new CustomType() == new CustomType(); 91 | } 92 | } 93 | 94 | public class CustomType 95 | { 96 | public static bool operator ==(CustomType c1, CustomType c2) 97 | { 98 | return true; 99 | } 100 | 101 | public static bool operator !=(CustomType c1, CustomType c2) 102 | { 103 | return true; 104 | } 105 | } 106 | "; 107 | 108 | Utilities.AssertPure(code); 109 | 110 | } 111 | 112 | [Test] 113 | public void MethodThatUsesImpureCustomEqualityComparisonOperatorIsImpure() 114 | { 115 | string code = @" 116 | using System; 117 | 118 | public class IsPureAttribute : Attribute 119 | { 120 | } 121 | 122 | public class MyClass 123 | { 124 | [IsPure] 125 | public static bool DoSomething() 126 | { 127 | return new CustomType() == new CustomType(); 128 | } 129 | } 130 | 131 | public class CustomType 132 | { 133 | static int state = 0; 134 | 135 | public static bool operator ==(CustomType c1, CustomType c2) 136 | { 137 | state++; 138 | return true; 139 | } 140 | 141 | public static bool operator !=(CustomType c1, CustomType c2) 142 | { 143 | return true; 144 | } 145 | } 146 | "; 147 | 148 | Utilities.AssertImpure(code); 149 | 150 | } 151 | 152 | 153 | 154 | [Test] 155 | public void PureCustomInequalityComparisonOperatorMethodIsConsideredPure() 156 | { 157 | string code = @" 158 | using System; 159 | 160 | public class IsPureAttribute : Attribute 161 | { 162 | } 163 | 164 | public class CustomType 165 | { 166 | [IsPure] 167 | public static bool operator !=(CustomType c1, CustomType c2) 168 | { 169 | return true; 170 | } 171 | 172 | public static bool operator ==(CustomType c1, CustomType c2) 173 | { 174 | return true; 175 | } 176 | } 177 | "; 178 | 179 | Utilities.AssertPure(code); 180 | 181 | } 182 | 183 | [Test] 184 | public void ImpureCustomInequalityComparisonOperatorMethodIsConsideredImpure() 185 | { 186 | string code = @" 187 | using System; 188 | 189 | public class IsPureAttribute : Attribute 190 | { 191 | } 192 | 193 | public class CustomType 194 | { 195 | static int state = 0; 196 | 197 | [IsPure] 198 | public static bool operator !=(CustomType c1, CustomType c2) 199 | { 200 | state++; 201 | return true; 202 | } 203 | 204 | public static bool operator ==(CustomType c1, CustomType c2) 205 | { 206 | return true; 207 | } 208 | } 209 | "; 210 | 211 | Utilities.AssertImpure(code); 212 | 213 | } 214 | 215 | [Test] 216 | public void MethodThatUsesPureCustomInequalityComparisonOperatorIsPure() 217 | { 218 | string code = @" 219 | using System; 220 | 221 | public class IsPureAttribute : Attribute 222 | { 223 | } 224 | 225 | public class MyClass 226 | { 227 | [IsPure] 228 | public static bool DoSomething() 229 | { 230 | return new CustomType() != new CustomType(); 231 | } 232 | } 233 | 234 | public class CustomType 235 | { 236 | public static bool operator !=(CustomType c1, CustomType c2) 237 | { 238 | return true; 239 | } 240 | 241 | public static bool operator ==(CustomType c1, CustomType c2) 242 | { 243 | return true; 244 | } 245 | } 246 | "; 247 | 248 | Utilities.AssertPure(code); 249 | 250 | } 251 | 252 | [Test] 253 | public void MethodThatUsesImpureCustomInequalityComparisonOperatorIsImpure() 254 | { 255 | string code = @" 256 | using System; 257 | 258 | public class IsPureAttribute : Attribute 259 | { 260 | } 261 | 262 | public class MyClass 263 | { 264 | [IsPure] 265 | public static bool DoSomething() 266 | { 267 | return new CustomType() != new CustomType(); 268 | } 269 | } 270 | 271 | public class CustomType 272 | { 273 | static int state = 0; 274 | 275 | public static bool operator !=(CustomType c1, CustomType c2) 276 | { 277 | state++; 278 | return true; 279 | } 280 | 281 | public static bool operator ==(CustomType c1, CustomType c2) 282 | { 283 | return true; 284 | } 285 | } 286 | "; 287 | 288 | Utilities.AssertImpure(code); 289 | 290 | } 291 | 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/CallingMethodsOnGenericClassesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using NUnit.Framework; 8 | using PurityAnalyzer.Tests.IsPureAttributeOnMethod.OverriddenMethods; 9 | 10 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 11 | { 12 | [TestFixture] 13 | public class CallingMethodsOnGenericClassesTests 14 | { 15 | [Test] 16 | public void CallingSimpleMethod_MethodIsPure() 17 | { 18 | string code = @" 19 | using System; 20 | 21 | public class IsPureAttribute : Attribute 22 | { 23 | } 24 | 25 | public class Class1 26 | { 27 | } 28 | 29 | public static class Module1 30 | { 31 | [IsPure] 32 | public static void DoSomething() 33 | { 34 | GenericClass.Method(new Class1()); 35 | } 36 | } 37 | 38 | public static class GenericClass 39 | { 40 | public static void Method(T param) 41 | { 42 | 43 | } 44 | } 45 | 46 | "; 47 | 48 | Utilities.AssertPure(code); 49 | } 50 | 51 | [Test] 52 | public void CallingMethodWhereTHasNoConstraintsAndTypeArgumentToStringMethodIsImpure_AndToStringMethodOfTIsUsed_MethodIsImpure() 53 | { 54 | string code = @" 55 | using System; 56 | 57 | public class IsPureAttribute : Attribute 58 | { 59 | } 60 | 61 | public class Class1 62 | { 63 | public static int state = 0; 64 | 65 | public override string ToString() 66 | { 67 | state++; 68 | return """"; 69 | } 70 | } 71 | 72 | public static class Module1 73 | { 74 | [IsPure] 75 | public static void DoSomething() 76 | { 77 | GenericClass.Method(new Class1()); 78 | } 79 | } 80 | 81 | public static class GenericClass 82 | { 83 | public static void Method(T param) 84 | { 85 | var str = param.ToString(); 86 | } 87 | } 88 | 89 | "; 90 | 91 | Utilities.AssertImpure(code); 92 | } 93 | 94 | 95 | [Test] 96 | public void CallingMethodWhereTHasNoConstraintsAndTypeArgumentToStringMethodIsImpure_AndNoObjectMethodOfTIsUsed_MethodIsPure() 97 | { 98 | string code = @" 99 | using System; 100 | 101 | public class IsPureAttribute : Attribute 102 | { 103 | } 104 | 105 | public class Class1 106 | { 107 | public static int state = 0; 108 | 109 | public override string ToString() 110 | { 111 | state++; 112 | return """"; 113 | } 114 | } 115 | 116 | public static class Module1 117 | { 118 | [IsPure] 119 | public static void DoSomething() 120 | { 121 | GenericClass.Method(new Class1()); 122 | } 123 | } 124 | 125 | public static class GenericClass 126 | { 127 | public static void Method(T param) 128 | { 129 | 130 | } 131 | }"; 132 | 133 | Utilities.AssertPure(code); 134 | } 135 | 136 | 137 | [Test] 138 | public void CallingCompiledMethodOnGenericClassWhereTHasNoConstraintsAndTypeArgumentToStringMethodIsImpure_AndTIsNotAnnotatedWithAttributes_MethodIsImpure() 139 | { 140 | string code = @" 141 | using System; 142 | using PurityAnalyzer.Tests.CompiledCsharpLib; 143 | 144 | public class IsPureAttribute : Attribute 145 | { 146 | } 147 | 148 | public class Class1 149 | { 150 | public static int state = 0; 151 | 152 | public override string ToString() 153 | { 154 | state++; 155 | return """"; 156 | } 157 | } 158 | 159 | public static class Module1 160 | { 161 | [IsPure] 162 | public static void DoSomething() 163 | { 164 | GenericClassAndTIsUsedAsObject.MethodThatUsesTAsObject(new Class1()); 165 | } 166 | } 167 | "; 168 | 169 | Utilities.AssertImpure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 170 | } 171 | 172 | 173 | [Test] 174 | public void CallingCompiledMethodOnGenericClassWhereTHasNoConstraintsAndTypeArgumentToStringMethodIsImpure_TIsAnnotatedWithNotUsedAsObjectAttribute_MethodIsPure() 175 | { 176 | string code = @" 177 | using System; 178 | using PurityAnalyzer.Tests.CompiledCsharpLib; 179 | 180 | 181 | public class IsPureAttribute : Attribute 182 | { 183 | } 184 | 185 | public class Class1 186 | { 187 | public static int state = 0; 188 | 189 | public override string ToString() 190 | { 191 | state++; 192 | return """"; 193 | } 194 | } 195 | 196 | public static class Module1 197 | { 198 | [IsPure] 199 | public static void DoSomething() 200 | { 201 | GenericClassAndTIsNotUsedAsObject.MethodThatDoesNotUseTAsObject(new Class1()); 202 | } 203 | }"; 204 | 205 | Utilities.AssertPure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 206 | } 207 | 208 | 209 | [Test] 210 | public void CallingCompiledMethodOnGenericClass_MethodHasAttributeToIndicateItDoesNotUseTAsObject_PassingClassWithImpureToString_KeepsMethodPure() 211 | { 212 | string code = @" 213 | using System; 214 | using PurityAnalyzer.Tests.CompiledCsharpLib; 215 | 216 | 217 | public class IsPureAttribute : Attribute 218 | { 219 | } 220 | 221 | public class Class1 222 | { 223 | public static int state = 0; 224 | 225 | public override string ToString() 226 | { 227 | state++; 228 | return """"; 229 | } 230 | } 231 | 232 | public static class Module1 233 | { 234 | [IsPure] 235 | public static void DoSomething() 236 | { 237 | GenericClassWithSomeMethodsThatUseTAsObjectAndSomeDoNot.MethodThatDoesNotUseTAsObject(new Class1()); 238 | } 239 | }"; 240 | 241 | Utilities.AssertPure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 242 | } 243 | 244 | [Test] 245 | public void CallingCompiledMethodOnGenericClass_MethodDoesNotHaveAttributeToIndicateItDoesNotUseTAsObject_PassingClassWithImpureToString_MakesMethodImpure() 246 | { 247 | string code = @" 248 | using System; 249 | using PurityAnalyzer.Tests.CompiledCsharpLib; 250 | 251 | 252 | public class IsPureAttribute : Attribute 253 | { 254 | } 255 | 256 | public class Class1 257 | { 258 | public static int state = 0; 259 | 260 | public override string ToString() 261 | { 262 | state++; 263 | return """"; 264 | } 265 | } 266 | 267 | public static class Module1 268 | { 269 | [IsPure] 270 | public static void DoSomething() 271 | { 272 | GenericClassWithSomeMethodsThatUseTAsObjectAndSomeDoNot.MethodThatUsesTAsObject(new Class1()); 273 | } 274 | }"; 275 | 276 | Utilities.AssertImpure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 277 | } 278 | 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/LinqTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using FluentAssertions; 3 | using NUnit.Framework; 4 | 5 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 6 | { 7 | [TestFixture] 8 | public class LinqTests 9 | { 10 | [Test] 11 | public void CallingEnumerableLinqMethodsKeepsMethodPure() 12 | { 13 | string code = @" 14 | using System; 15 | using System.Linq; 16 | 17 | public class IsPureAttribute : Attribute 18 | { 19 | } 20 | 21 | public static class Module1 22 | { 23 | [IsPure] 24 | public static string DoSomething(int[] data) 25 | { 26 | return 27 | data 28 | .Where(x => x > 0) 29 | .Select(x => x + 1) 30 | .SelectMany(x => new []{x, x * 2}) 31 | .GroupBy(x => x > 2) 32 | .Select(x => x.Key.ToString()) 33 | .First(); 34 | } 35 | }"; 36 | Utilities.AssertPure(code); 37 | } 38 | 39 | [Test] 40 | public void CallingSelectMethodAndPassedLambdaCallsImpureMethodMakesMethodImpure() 41 | { 42 | string code = @" 43 | using System; 44 | using System.Linq; 45 | 46 | public class IsPureAttribute : Attribute 47 | { 48 | } 49 | 50 | public static class Module1 51 | { 52 | [IsPure] 53 | public static string DoSomething(int[] data) 54 | { 55 | return 56 | data 57 | .Select(x => ImpureMethod(x)) 58 | .First(); 59 | } 60 | 61 | static int state = 0; 62 | 63 | public static string ImpureMethod(int input) 64 | { 65 | state++; 66 | 67 | return """"; 68 | } 69 | 70 | }"; 71 | 72 | Utilities.AssertImpure(code); 73 | } 74 | 75 | [Test] 76 | public void CallingSelectMethodAndPassedLambdaIncrementsFieldMakesMethodImpure() 77 | { 78 | string code = @" 79 | using System; 80 | using System.Linq; 81 | 82 | public class IsPureAttribute : Attribute 83 | { 84 | } 85 | 86 | public static class Module1 87 | { 88 | [IsPure] 89 | public static int DoSomething(int[] data) 90 | { 91 | return 92 | data 93 | .Select(x => state++) 94 | .First(); 95 | } 96 | 97 | static int state = 0; 98 | }"; 99 | 100 | Utilities.AssertImpure(code); 101 | } 102 | 103 | [Test] 104 | public void CallingSelectMethodAndPassedLambdaReadsMutableFieldMakesMethodImpure() 105 | { 106 | string code = @" 107 | using System; 108 | using System.Linq; 109 | 110 | public class IsPureAttribute : Attribute 111 | { 112 | } 113 | 114 | public static class Module1 115 | { 116 | [IsPure] 117 | public static int DoSomething(int[] data) 118 | { 119 | return 120 | data 121 | .Select(x => state) 122 | .First(); 123 | } 124 | 125 | static int state = 0; 126 | }"; 127 | 128 | Utilities.AssertImpure(code); 129 | } 130 | 131 | [Test] 132 | public void CallingSelectMethodPassingImpureMethodGroupMakesMethodImpure() 133 | { 134 | string code = @" 135 | using System; 136 | using System.Linq; 137 | 138 | public class IsPureAttribute : Attribute 139 | { 140 | } 141 | 142 | public static class Module1 143 | { 144 | [IsPure] 145 | public static string DoSomething(int[] data) 146 | { 147 | return 148 | data 149 | .Select(ImpureMethod) 150 | .First(); 151 | } 152 | 153 | static int state = 0; 154 | 155 | public static string ImpureMethod(int input) 156 | { 157 | state++; 158 | 159 | return """"; 160 | } 161 | 162 | }"; 163 | 164 | Utilities.AssertImpure(code); 165 | } 166 | 167 | 168 | [Test] 169 | public void CallingSelectMethodPassingPureMethodGroupKeepsMethodPure() 170 | { 171 | string code = @" 172 | using System; 173 | using System.Linq; 174 | 175 | public class IsPureAttribute : Attribute 176 | { 177 | } 178 | 179 | public static class Module1 180 | { 181 | [IsPure] 182 | public static string DoSomething(int[] data) 183 | { 184 | return 185 | data 186 | .Select(PureMethod) 187 | .First(); 188 | } 189 | 190 | static int state = 0; 191 | 192 | public static string PureMethod(int input) 193 | { 194 | return """"; 195 | } 196 | 197 | }"; 198 | 199 | Utilities.AssertPure(code); 200 | } 201 | 202 | [Test] 203 | public void CallingEnumerableLinqMethodsViaQuerySyntaxKeepsMethodPure() 204 | { 205 | string code = @" 206 | using System; 207 | using System.Linq; 208 | 209 | public class IsPureAttribute : Attribute 210 | { 211 | } 212 | 213 | public static class Module1 214 | { 215 | [IsPure] 216 | public static string DoSomething(int[] data) 217 | { 218 | var result = 219 | from x in data 220 | where x > 0 221 | let x2 = x + 1 222 | group x2 by x2 > 2 into g 223 | select g.Key; 224 | 225 | return result.First().ToString(); 226 | } 227 | }"; 228 | 229 | Utilities.AssertPure(code); 230 | 231 | } 232 | 233 | [Test] 234 | public void CallingEnumerableLinqMethodsViaQuerySyntaxAndLetUsesImpureMethodMakesMethodImpure() 235 | { 236 | string code = @" 237 | using System; 238 | using System.Linq; 239 | 240 | public class IsPureAttribute : Attribute 241 | { 242 | } 243 | 244 | public static class Module1 245 | { 246 | [IsPure] 247 | public static bool DoSomething(int[] data) 248 | { 249 | var result = 250 | from x in data 251 | where x > 0 252 | let x2 = ImpureMethod( x + 1) 253 | group x2 by x2 > 2 into g 254 | select g.Key; 255 | 256 | return result.First(); 257 | } 258 | 259 | static int state = 0; 260 | 261 | public static int ImpureMethod(int input) 262 | { 263 | return state++; 264 | } 265 | }"; 266 | 267 | Utilities.AssertImpure(code); 268 | 269 | } 270 | 271 | 272 | [Test] 273 | public void CallingToListOnEnumerableOfStringIsPure() 274 | { 275 | string code = @" 276 | using System; 277 | using System.Linq; 278 | using System.Collections.Generic; 279 | 280 | public class IsPureAttribute : Attribute 281 | { 282 | } 283 | 284 | public static class Module1 285 | { 286 | [IsPure] 287 | public static List DoSomething() 288 | { 289 | IEnumerable Strings() 290 | { 291 | yield return ""s1""; 292 | yield return ""s2""; 293 | } 294 | 295 | return Strings().ToList(); 296 | } 297 | 298 | 299 | }"; 300 | 301 | Utilities.AssertPure(code); 302 | 303 | } 304 | 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/InvokingStaticMethodTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 5 | { 6 | [TestFixture] 7 | public class InvokingStaticMethodTests 8 | { 9 | [Test] 10 | public void InvokingStaticMethodOnClassWithPureStaticConstructorKeepsMethodPure() 11 | { 12 | string code = @" 13 | using System; 14 | 15 | public class IsPureAttribute : Attribute 16 | { 17 | } 18 | 19 | public static class Module1 20 | { 21 | [IsPure] 22 | public static int DoSomething() 23 | { 24 | return StaticClass.DoSomething(); 25 | } 26 | } 27 | 28 | public static class StaticClass 29 | { 30 | static StaticClass() 31 | { 32 | 33 | } 34 | 35 | public static int DoSomething() => 1; 36 | } 37 | "; 38 | 39 | Utilities.AssertPure(code); 40 | 41 | } 42 | 43 | [Test] 44 | public void InvokingStaticMethodOnClassWithPureStaticFieldInitializerKeepsMethodPure() 45 | { 46 | string code = @" 47 | using System; 48 | 49 | public class IsPureAttribute : Attribute 50 | { 51 | } 52 | 53 | public static class Module1 54 | { 55 | [IsPure] 56 | public static int DoSomething() 57 | { 58 | return StaticClass.DoSomething(); 59 | } 60 | } 61 | 62 | public static class StaticClass 63 | { 64 | public static int field = 0; 65 | 66 | public static int DoSomething() => 1; 67 | } 68 | "; 69 | 70 | Utilities.AssertPure(code); 71 | 72 | } 73 | 74 | [Test] 75 | public void InvokingStaticMethodOnClassWithPureStaticPropertyInitializerKeepsMethodPure() 76 | { 77 | string code = @" 78 | using System; 79 | 80 | public class IsPureAttribute : Attribute 81 | { 82 | } 83 | 84 | public static class Module1 85 | { 86 | [IsPure] 87 | public static int DoSomething() 88 | { 89 | return StaticClass.DoSomething(); 90 | } 91 | } 92 | 93 | public static class StaticClass 94 | { 95 | public static int Property {get;set;} = 0; 96 | 97 | public static int DoSomething() => 1; 98 | } 99 | "; 100 | 101 | Utilities.AssertPure(code); 102 | 103 | } 104 | 105 | [Test] 106 | public void InvokingStaticMethodOnClassWithImpureStaticConstructorMakesMethodImpure() 107 | { 108 | string code = @" 109 | using System; 110 | 111 | public class IsPureAttribute : Attribute 112 | { 113 | } 114 | 115 | public static class Module1 116 | { 117 | [IsPure] 118 | public static int DoSomething() 119 | { 120 | return StaticClass.DoSomething(); 121 | } 122 | } 123 | 124 | public static class StaticClass 125 | { 126 | static StaticClass() 127 | { 128 | AnotherClass.state++; 129 | } 130 | 131 | public static int DoSomething() => 1; 132 | } 133 | 134 | public static class AnotherClass 135 | { 136 | public static int state = 0; 137 | } 138 | 139 | "; 140 | 141 | Utilities.AssertImpure(code); 142 | 143 | } 144 | 145 | [Test] 146 | public void InvokingStaticMethodOnClassWithImpureStaticFieldInitializerMakesMethodImpure() 147 | { 148 | string code = @" 149 | using System; 150 | 151 | public class IsPureAttribute : Attribute 152 | { 153 | } 154 | 155 | public static class Module1 156 | { 157 | [IsPure] 158 | public static int DoSomething() 159 | { 160 | return StaticClass.DoSomething(); 161 | } 162 | } 163 | 164 | public static class StaticClass 165 | { 166 | public static int field = AnotherClass.state++; 167 | 168 | public static int DoSomething() => 1; 169 | } 170 | 171 | public static class AnotherClass 172 | { 173 | public static int state = 0; 174 | } 175 | "; 176 | 177 | Utilities.AssertImpure(code); 178 | 179 | } 180 | 181 | [Test] 182 | public void InvokingStaticMethodOnClassWithImpureStaticPropertyInitializerMakesMethodImpure() 183 | { 184 | string code = @" 185 | using System; 186 | 187 | public class IsPureAttribute : Attribute 188 | { 189 | } 190 | 191 | public static class Module1 192 | { 193 | [IsPure] 194 | public static int DoSomething() 195 | { 196 | return StaticClass.DoSomething(); 197 | } 198 | } 199 | 200 | public static class StaticClass 201 | { 202 | public static int Property {get;set;} = AnotherClass.state++; 203 | 204 | public static int DoSomething() => 1; 205 | } 206 | 207 | public static class AnotherClass 208 | { 209 | public static int state = 0; 210 | } 211 | "; 212 | 213 | Utilities.AssertImpure(code); 214 | 215 | } 216 | 217 | [Test] 218 | public void InvokingStaticMethodOnClassWithImpureInstanceConstructorKeepsMethodPure() 219 | { 220 | string code = @" 221 | using System; 222 | 223 | public class IsPureAttribute : Attribute 224 | { 225 | } 226 | 227 | public static class Module1 228 | { 229 | [IsPure] 230 | public static int DoSomething() 231 | { 232 | return Class1.DoSomething(); 233 | } 234 | } 235 | 236 | public class Class1 237 | { 238 | public Class1() 239 | { 240 | AnotherClass.state++; 241 | } 242 | 243 | public static int DoSomething() => 1; 244 | } 245 | 246 | public static class AnotherClass 247 | { 248 | public static int state = 0; 249 | } 250 | 251 | "; 252 | 253 | Utilities.AssertPure(code); 254 | 255 | } 256 | 257 | [Test] 258 | public void InvokingStaticMethodOnClassWithImpureInstanceFieldInitializerKeepsMethodPure() 259 | { 260 | string code = @" 261 | using System; 262 | 263 | public class IsPureAttribute : Attribute 264 | { 265 | } 266 | 267 | public static class Module1 268 | { 269 | [IsPure] 270 | public static int DoSomething() 271 | { 272 | return Class1.DoSomething(); 273 | } 274 | } 275 | 276 | public class Class1 277 | { 278 | public int field = AnotherClass.state++; 279 | 280 | public static int DoSomething() => 1; 281 | } 282 | 283 | public static class AnotherClass 284 | { 285 | public static int state = 0; 286 | } 287 | "; 288 | 289 | Utilities.AssertPure(code); 290 | 291 | } 292 | 293 | [Test] 294 | public void InvokingStaticMethodOnClassWithImpureInstancePropertyInitializerKeepsMethodPure() 295 | { 296 | string code = @" 297 | using System; 298 | 299 | public class IsPureAttribute : Attribute 300 | { 301 | } 302 | 303 | public static class Module1 304 | { 305 | [IsPure] 306 | public static int DoSomething() 307 | { 308 | return Class1.DoSomething(); 309 | } 310 | } 311 | 312 | public class Class1 313 | { 314 | public int Property {get;set;} = AnotherClass.state++; 315 | 316 | public static int DoSomething() => 1; 317 | } 318 | 319 | public static class AnotherClass 320 | { 321 | public static int state = 0; 322 | } 323 | "; 324 | 325 | Utilities.AssertPure(code); 326 | 327 | } 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnMethod/ArrayTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnMethod 5 | { 6 | [TestFixture] 7 | public class ArrayTests 8 | { 9 | //Although arrays are mutable, I assume reading from an array passed as a parameter to be a pure operation 10 | //It is better to pass ImmutableArray though 11 | [Test] 12 | public void MethodThatReadsArrayElementFromInputIsPure() 13 | { 14 | string code = @" 15 | using System; 16 | 17 | public class IsPureAttribute : Attribute 18 | { 19 | } 20 | 21 | public static class Module1 22 | { 23 | [IsPure] 24 | public static string DoSomething(string[] input) 25 | { 26 | return input[0]; 27 | } 28 | }"; 29 | 30 | Utilities.AssertPure(code); 31 | 32 | } 33 | 34 | [Test] 35 | public void MethodThatSetsArrayElementFromInputIsImpure() 36 | { 37 | string code = @" 38 | using System; 39 | 40 | public class IsPureAttribute : Attribute 41 | { 42 | } 43 | 44 | public static class Module1 45 | { 46 | [IsPure] 47 | public static string DoSomething(string[] input) 48 | { 49 | input[0] = """"; 50 | return """"; 51 | } 52 | }"; 53 | 54 | Utilities.AssertImpure(code); 55 | 56 | } 57 | 58 | //It is impure because arrays are mutable 59 | [Test] 60 | public void MethodThatGetsElementInArrayDefinedAsAStaticFieldIsImpure() 61 | { 62 | string code = @" 63 | using System; 64 | 65 | public class IsPureAttribute : Attribute 66 | { 67 | } 68 | 69 | public static class Module1 70 | { 71 | static readonly int[] arr = {1,2,3}; 72 | 73 | [IsPure] 74 | public static string DoSomething() 75 | { 76 | var v = arr[1]; 77 | return """"; 78 | } 79 | }"; 80 | 81 | Utilities.AssertImpure(code); 82 | 83 | } 84 | 85 | 86 | [Test] 87 | public void MethodThatSetsElementInArrayDefinedAsAStaticFieldIsImpure() 88 | { 89 | string code = @" 90 | using System; 91 | 92 | public class IsPureAttribute : Attribute 93 | { 94 | } 95 | 96 | public static class Module1 97 | { 98 | static readonly int[] arr = {1,2,3}; 99 | 100 | [IsPure] 101 | public static string DoSomething() 102 | { 103 | arr[1] = 2; 104 | return """"; 105 | } 106 | }"; 107 | 108 | Utilities.AssertImpure(code); 109 | 110 | } 111 | 112 | 113 | [Test] 114 | public void MethodThatGetsElementInArrayCreatedInsideMethodIsPure() 115 | { 116 | string code = @" 117 | using System; 118 | 119 | public class IsPureAttribute : Attribute 120 | { 121 | } 122 | 123 | public static class Module1 124 | { 125 | 126 | [IsPure] 127 | public static string DoSomething() 128 | { 129 | int[] arr = new []{1,2,3}; 130 | var v = arr[1]; 131 | return """"; 132 | } 133 | }"; 134 | 135 | Utilities.AssertPure(code); 136 | 137 | } 138 | 139 | [Test] 140 | public void MethodThatGetsElementInArrayCreatedInsideMethodAndSpecifyingElementTypeIsPure() 141 | { 142 | string code = @" 143 | using System; 144 | 145 | public class IsPureAttribute : Attribute 146 | { 147 | } 148 | 149 | public static class Module1 150 | { 151 | 152 | [IsPure] 153 | public static string DoSomething() 154 | { 155 | int[] arr = new int[]{1,2,3}; 156 | var v = arr[1]; 157 | return """"; 158 | } 159 | }"; 160 | 161 | Utilities.AssertPure(code); 162 | 163 | } 164 | 165 | 166 | [Test] 167 | public void MethodThatSetsElementInArrayCreatedInsideMethodIsPure() 168 | { 169 | string code = @" 170 | using System; 171 | 172 | public class IsPureAttribute : Attribute 173 | { 174 | } 175 | 176 | public static class Module1 177 | { 178 | 179 | [IsPure] 180 | public static string DoSomething() 181 | { 182 | int[] arr = new []{1,2,3}; 183 | arr[1] = 5; 184 | return """"; 185 | } 186 | }"; 187 | 188 | Utilities.AssertPure(code); 189 | 190 | } 191 | 192 | [Test] 193 | public void MethodThatSetsElementInArrayCreatedInsideMethodAndSpecifyingElementTypeIsPure() 194 | { 195 | string code = @" 196 | using System; 197 | 198 | public class IsPureAttribute : Attribute 199 | { 200 | } 201 | 202 | public static class Module1 203 | { 204 | 205 | [IsPure] 206 | public static string DoSomething() 207 | { 208 | int[] arr = new int[]{1,2,3}; 209 | arr[1] = 5; 210 | return """"; 211 | } 212 | }"; 213 | 214 | Utilities.AssertPure(code); 215 | 216 | } 217 | 218 | 219 | [Test] 220 | public void MethodThatGetsElementInArrayCreatedInsideMethodWithoutNewKeywordIsPure() 221 | { 222 | string code = @" 223 | using System; 224 | 225 | public class IsPureAttribute : Attribute 226 | { 227 | } 228 | 229 | public static class Module1 230 | { 231 | 232 | [IsPure] 233 | public static string DoSomething() 234 | { 235 | int[] arr = {1,2,3}; 236 | var v = arr[1]; 237 | return """"; 238 | } 239 | }"; 240 | 241 | Utilities.AssertPure(code); 242 | 243 | } 244 | 245 | 246 | [Test] 247 | public void MethodThatSetsElementInArrayCreatedInsideMethodWithoutNewKeywordIsPure() 248 | { 249 | string code = @" 250 | using System; 251 | 252 | public class IsPureAttribute : Attribute 253 | { 254 | } 255 | 256 | public static class Module1 257 | { 258 | 259 | [IsPure] 260 | public static string DoSomething() 261 | { 262 | int[] arr = {1,2,3}; 263 | arr[1] = 5; 264 | return """"; 265 | } 266 | }"; 267 | 268 | Utilities.AssertPure(code); 269 | 270 | } 271 | 272 | [Test] 273 | public void MethodThatReadsLengthOfArrayCreatedInMethodIsPure() 274 | { 275 | string code = @" 276 | using System; 277 | 278 | public class IsPureAttribute : Attribute 279 | { 280 | } 281 | 282 | public static class Module1 283 | { 284 | 285 | [IsPure] 286 | public static int DoSomething() 287 | { 288 | int[] arr = {1,2,3}; 289 | var length = arr.Length; 290 | return length; 291 | } 292 | }"; 293 | 294 | Utilities.AssertPure(code); 295 | 296 | } 297 | 298 | [Test] 299 | public void MethodThatReadsLengthOfArrayPassedAsParameterIsPure() 300 | { 301 | string code = @" 302 | using System; 303 | 304 | public class IsPureAttribute : Attribute 305 | { 306 | } 307 | 308 | public static class Module1 309 | { 310 | 311 | [IsPure] 312 | public static int DoSomething(int[] arr) 313 | { 314 | var length = arr.Length; 315 | return length; 316 | } 317 | }"; 318 | 319 | Utilities.AssertPure(code); 320 | 321 | } 322 | 323 | } 324 | 325 | 326 | } 327 | -------------------------------------------------------------------------------- /PurityAnalyzer.Tests/IsPureAttributeOnProperty/IsPureAttributeOnPropertyTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace PurityAnalyzer.Tests.IsPureAttributeOnProperty 5 | { 6 | [TestFixture] 7 | public class IsPureAttributeOnPropertyTests 8 | { 9 | [Test] 10 | public void TestIsPureAttributeOnStaticAutomaticReadonlyProperty() 11 | { 12 | string code = @" 13 | using System; 14 | 15 | public class IsPureAttribute : Attribute 16 | { 17 | } 18 | 19 | public class Class1 20 | { 21 | static Class1() 22 | { 23 | Prop1 = 1; 24 | } 25 | 26 | [IsPure] 27 | public static int Prop1 {get;} 28 | }"; 29 | 30 | Utilities.AssertPure(code); 31 | 32 | } 33 | 34 | [Test] 35 | public void TestIsPureAttributeOnStaticPropertyThatIsImpure() 36 | { 37 | string code = @" 38 | using System; 39 | 40 | public class IsPureAttribute : Attribute 41 | { 42 | } 43 | 44 | public class Class1 45 | { 46 | static int a; 47 | 48 | [IsPure] 49 | public static int Prop1 50 | { 51 | get 52 | { 53 | return a++; 54 | } 55 | } 56 | }"; 57 | 58 | Utilities.AssertImpure(code); 59 | 60 | } 61 | 62 | 63 | [Test] 64 | public void TestIsPureAttributeOnStaticPropertyThatIsImpureAndThatIsExpressionBodied() 65 | { 66 | string code = @" 67 | using System; 68 | 69 | public class IsPureAttribute : Attribute 70 | { 71 | } 72 | 73 | public class Class1 74 | { 75 | static int a; 76 | 77 | [IsPure] 78 | public static int Prop1 => a++; 79 | }"; 80 | 81 | Utilities.AssertImpure(code); 82 | 83 | } 84 | 85 | [Test] 86 | public void TestIsPureAttributeOnStaticPropertyThatIsImpureAndThatIsExpressionBodiedGet() 87 | { 88 | string code = @" 89 | using System; 90 | 91 | public class IsPureAttribute : Attribute 92 | { 93 | } 94 | 95 | public class Class1 96 | { 97 | static int a; 98 | 99 | [IsPure] 100 | public static int Prop1 101 | { 102 | get => a++; 103 | } 104 | }"; 105 | 106 | Utilities.AssertImpure(code); 107 | 108 | } 109 | 110 | [Test] 111 | public void PropertyWhoseGetterInvokesAPureExceptLocallyPropertyGetterAndSetterGetOnNewlyCreatedCompiledObjectIsPure() 112 | { 113 | string code = @" 114 | using System; 115 | using PurityAnalyzer.Tests.CompiledCsharpLib; 116 | 117 | public class IsPureAttribute : Attribute 118 | { 119 | } 120 | 121 | public static class Module1 122 | { 123 | [IsPure] 124 | public static int Prop 125 | { 126 | get 127 | { 128 | var instance = new MutableClassWithPurePropertiesExceptLocally(); 129 | 130 | return instance.PureExceptLocallyPropertyGetterAndSetter; 131 | } 132 | } 133 | }"; 134 | 135 | Utilities.AssertPure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 136 | } 137 | 138 | [Test] 139 | public void ExpressionBodiedPropertyThatInvokesAPureExceptLocallyPropertyGetterAndSetterGetOnNewlyCreatedCompiledObjectIsPure() 140 | { 141 | string code = @" 142 | using System; 143 | using PurityAnalyzer.Tests.CompiledCsharpLib; 144 | 145 | public class IsPureAttribute : Attribute 146 | { 147 | } 148 | 149 | public static class Module1 150 | { 151 | [IsPure] 152 | public static int Prop => new MutableClassWithPurePropertiesExceptLocally().PureExceptLocallyPropertyGetterAndSetter; 153 | }"; 154 | 155 | Utilities.AssertPure(code, Utilities.GetTestsCompiledCsharpLibProjectReference()); 156 | } 157 | 158 | 159 | [Test] 160 | public void CastFromNewObjectAndPassResultToPureExceptReadLocallyMethodViaTwoVariablesAndOneIsInitializedInDifferentStatement_KeepsMethodPure() 161 | { 162 | string code = @" 163 | using System; 164 | 165 | public class IsPureAttribute : Attribute 166 | { 167 | } 168 | 169 | public class Base 170 | { 171 | public virtual int Method() => 1; 172 | } 173 | 174 | public class Derived : Base 175 | { 176 | int state = 0; 177 | public override int Method() => state++; 178 | } 179 | 180 | public static class Module1 181 | { 182 | [IsPure] 183 | public static int DoSomething 184 | { 185 | get 186 | { 187 | Class1 class1 = new Class1(); 188 | 189 | Base x; 190 | 191 | x = new Derived(); 192 | 193 | Base y = x; 194 | 195 | class1.PureExceptReadLocally(y); 196 | 197 | return 1; 198 | } 199 | } 200 | 201 | } 202 | 203 | public class Class1 204 | { 205 | int state = 0; 206 | 207 | public int PureExceptReadLocally(Base x) 208 | { 209 | return state; 210 | } 211 | } 212 | 213 | "; 214 | 215 | Utilities.AssertPure(code); 216 | 217 | } 218 | 219 | //A pure except locally method could decide to store the parameter inside a field 220 | [Test] 221 | public void CastFromNewObjectAndPassResultToPureExceptLocallyMethodViaTwoVariablesAndOneIsInitializedInDifferentStatement_MakesMethodImpure() 222 | { 223 | string code = @" 224 | using System; 225 | 226 | public class IsPureAttribute : Attribute 227 | { 228 | } 229 | 230 | public class Base 231 | { 232 | public virtual int Method() => 1; 233 | } 234 | 235 | public class Derived : Base 236 | { 237 | int state = 0; 238 | public override int Method() => state++; 239 | } 240 | 241 | public static class Module1 242 | { 243 | [IsPure] 244 | public static int DoSomething 245 | { 246 | get 247 | { 248 | Class1 class1 = new Class1(); 249 | 250 | Base x; 251 | 252 | x = new Derived(); 253 | 254 | Base y = x; 255 | 256 | class1.PureExceptLocally(y); 257 | 258 | return 1; 259 | } 260 | } 261 | 262 | } 263 | 264 | public class Class1 265 | { 266 | int state = 0; 267 | 268 | public int PureExceptLocally(Base x) 269 | { 270 | return state++; 271 | } 272 | } 273 | "; 274 | 275 | Utilities.AssertImpure(code); 276 | 277 | } 278 | 279 | [Test] 280 | public void TestIsPureAttributeOnStaticAutomaticReadWriteProperty() 281 | { 282 | string code = @" 283 | using System; 284 | 285 | public class IsPureAttribute : Attribute 286 | { 287 | } 288 | 289 | public class Class1 290 | { 291 | [IsPure] 292 | public static int Prop1 {get; set;} 293 | }"; 294 | 295 | Utilities.AssertImpure(code); 296 | 297 | } 298 | 299 | [Test] 300 | public void TestIsPureAttributeOnInstanceAutomaticReadWriteProperty() 301 | { 302 | string code = @" 303 | using System; 304 | 305 | public class IsPureAttribute : Attribute 306 | { 307 | } 308 | 309 | public class Class1 310 | { 311 | [IsPure] 312 | public int Prop1 {get; set;} 313 | }"; 314 | 315 | Utilities.AssertImpure(code); 316 | 317 | } 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /PurityAnalyzer.Vsix/VSPackage.resx: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | text/microsoft-resx 120 | 121 | 122 | 2.0 123 | 124 | 125 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 126 | 127 | 128 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 129 | 130 | 131 | 132 | VSPackage Extension 133 | 134 | 135 | VSPackage Visual Studio Extension Detailed Info 136 | 137 | 138 | Resources\VSPackage.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 139 | 140 | --------------------------------------------------------------------------------