diff --git a/ConventionTests.sln b/ConventionTests.sln index ca5b3ac..7be2a13 100644 --- a/ConventionTests.sln +++ b/ConventionTests.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.20617.1 PREVIEW +VisualStudioVersion = 12.0.20623.1 VSUPREVIEW MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConventionTests", "ConventionTests\ConventionTests.csproj", "{1E12EA0C-9182-4029-991A-B0B9D38F5783}" EndProject @@ -23,6 +23,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{35B8E1 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestAssembly", "TestAssembly\TestAssembly.csproj", "{D5A0D078-C660-4654-8A14-DDC816BEBC54}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{E971721A-1271-4359-8F76-9FF17C29582B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp.Tests", "SampleApp.Tests\SampleApp.Tests.csproj", "{8FEF48A8-6FF5-4B65-B84A-6690D735C703}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp", "SampleApp\SampleApp.csproj", "{56467A5A-7DD6-45B3-A84C-144A3C5D0C7A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -45,8 +51,20 @@ Global {D5A0D078-C660-4654-8A14-DDC816BEBC54}.Debug|Any CPU.Build.0 = Debug|Any CPU {D5A0D078-C660-4654-8A14-DDC816BEBC54}.Release|Any CPU.ActiveCfg = Release|Any CPU {D5A0D078-C660-4654-8A14-DDC816BEBC54}.Release|Any CPU.Build.0 = Release|Any CPU + {8FEF48A8-6FF5-4B65-B84A-6690D735C703}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FEF48A8-6FF5-4B65-B84A-6690D735C703}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FEF48A8-6FF5-4B65-B84A-6690D735C703}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FEF48A8-6FF5-4B65-B84A-6690D735C703}.Release|Any CPU.Build.0 = Release|Any CPU + {56467A5A-7DD6-45B3-A84C-144A3C5D0C7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56467A5A-7DD6-45B3-A84C-144A3C5D0C7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56467A5A-7DD6-45B3-A84C-144A3C5D0C7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56467A5A-7DD6-45B3-A84C-144A3C5D0C7A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {8FEF48A8-6FF5-4B65-B84A-6690D735C703} = {E971721A-1271-4359-8F76-9FF17C29582B} + {56467A5A-7DD6-45B3-A84C-144A3C5D0C7A} = {E971721A-1271-4359-8F76-9FF17C29582B} + EndGlobalSection EndGlobal diff --git a/ConventionTests.sln.DotSettings b/ConventionTests.sln.DotSettings index 2941484..20dae96 100644 --- a/ConventionTests.sln.DotSettings +++ b/ConventionTests.sln.DotSettings @@ -1,4 +1,5 @@  + True DO_NOT_SHOW <?xml version="1.0" encoding="utf-16"?><Profile name="all"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSRemoveCodeRedundancies>True</CSRemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSUseVar><BehavourStyle>CAN_CHANGE_TO_IMPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_IMPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_IMPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CSReorderTypeMembers>True</CSReorderTypeMembers></Profile> all diff --git a/SampleApp.Tests/DomainTests.cs b/SampleApp.Tests/DomainTests.cs new file mode 100644 index 0000000..bb34396 --- /dev/null +++ b/SampleApp.Tests/DomainTests.cs @@ -0,0 +1,33 @@ +namespace SampleApp.Tests +{ + using System.Linq; + using NUnit.Framework; + using SampleApp.Domain; + using TestStack.ConventionTests; + using TestStack.ConventionTests.ConventionData; + using TestStack.ConventionTests.Conventions; + + [TestFixture] + public class DomainTests + { + readonly Types domainEntities; + + public DomainTests() + { + domainEntities = Types.InAssemblyOf("Domain Entities", + types => types.Where(t=>t.Namespace.StartsWith("SampleApp.Domain"))); + } + + [Test] + public void DomainClassesShouldHaveDefaultConstructor() + { + Convention.Is(new AllClassesHaveDefaultConstructor(), domainEntities); + } + + [Test] + public void DomainClassesShouldHaveVirtualProperties() + { + Convention.Is(new AllMethodsAreVirtual(), domainEntities); + } + } +} \ No newline at end of file diff --git a/SampleApp.Tests/DtoTests.cs b/SampleApp.Tests/DtoTests.cs new file mode 100644 index 0000000..817017b --- /dev/null +++ b/SampleApp.Tests/DtoTests.cs @@ -0,0 +1,19 @@ +namespace SampleApp.Tests +{ + using NUnit.Framework; + using SampleApp.Dtos; + using TestStack.ConventionTests; + using TestStack.ConventionTests.ConventionData; + using TestStack.ConventionTests.Conventions; + + [TestFixture] + public class DtoTests + { + [Test] + public void DtosMustOnlyExistInDtoNameSpace() + { + Convention.Is(new ClassTypeHasSpecificNamespace(t=>t.Name.EndsWith("Dto"), "SampleApp.Dtos", "Dto"), + Types.InAssemblyOf()); + } + } +} \ No newline at end of file diff --git a/SampleApp.Tests/MvcTests.cs b/SampleApp.Tests/MvcTests.cs new file mode 100644 index 0000000..484b2fc --- /dev/null +++ b/SampleApp.Tests/MvcTests.cs @@ -0,0 +1,18 @@ +namespace SampleApp.Tests +{ + using NUnit.Framework; + using SampleApp.Mvc; + using TestStack.ConventionTests; + using TestStack.ConventionTests.ConventionData; + using TestStack.ConventionTests.Conventions; + + [TestFixture] + public class MvcTests + { + [Test] + public void MvcControllerTest() + { + Convention.Is(new MvcControllerNameAndBaseClassConvention(), Types.InAssemblyOf()); + } + } +} \ No newline at end of file diff --git a/SampleApp.Tests/Properties/AssemblyInfo.cs b/SampleApp.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0fbf587 --- /dev/null +++ b/SampleApp.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SampleApp.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SampleApp.Tests")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7b4d34d3-0454-4676-8f40-0ee1c01b984f")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SampleApp.Tests/SampleApp.Tests.csproj b/SampleApp.Tests/SampleApp.Tests.csproj new file mode 100644 index 0000000..f990830 --- /dev/null +++ b/SampleApp.Tests/SampleApp.Tests.csproj @@ -0,0 +1,94 @@ + + + + + Debug + AnyCPU + {8FEF48A8-6FF5-4B65-B84A-6690D735C703} + Library + Properties + SampleApp.Tests + SampleApp.Tests + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll + + + ..\packages\NUnit.2.6.2\lib\nunit.framework.dll + + + + + ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll + + + ..\packages\Microsoft.AspNet.WebApi.Client.4.0.30506.0\lib\net40\System.Net.Http.Formatting.dll + + + ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll + + + ..\packages\Microsoft.AspNet.WebApi.Core.4.0.30506.0\lib\net40\System.Web.Http.dll + + + + + + + + + + + + + + + + + + + + {56467A5A-7DD6-45B3-A84C-144A3C5D0C7A} + SampleApp + + + {955B0236-089F-434D-BA02-63A1E24C2B7C} + TestStack.ConventionTests + + + + + + + + + + + \ No newline at end of file diff --git a/SampleApp.Tests/SampleApp.Tests.ncrunchproject b/SampleApp.Tests/SampleApp.Tests.ncrunchproject new file mode 100644 index 0000000..c4410bf --- /dev/null +++ b/SampleApp.Tests/SampleApp.Tests.ncrunchproject @@ -0,0 +1,22 @@ + + false + false + false + true + false + false + false + false + true + true + false + true + true + 60000 + + + + AutoDetect + STA + x86 + \ No newline at end of file diff --git a/SampleApp.Tests/SqlScriptTests.cs b/SampleApp.Tests/SqlScriptTests.cs new file mode 100644 index 0000000..5bf401f --- /dev/null +++ b/SampleApp.Tests/SqlScriptTests.cs @@ -0,0 +1,18 @@ +namespace SampleApp.Tests +{ + using NUnit.Framework; + using SampleApp.Domain; + using TestStack.ConventionTests; + using TestStack.ConventionTests.ConventionData; + using TestStack.ConventionTests.Conventions; + + [TestFixture] + public class SqlScriptTests + { + [Test] + public void SqlScriptsShouldBeEmbeddedResources() + { + Convention.Is(new FilesAreEmbeddedResources(".sql"), new ProjectFiles(typeof(DomainClass).Assembly)); + } + } +} \ No newline at end of file diff --git a/SampleApp.Tests/WebApiTests.cs b/SampleApp.Tests/WebApiTests.cs new file mode 100644 index 0000000..bb60c74 --- /dev/null +++ b/SampleApp.Tests/WebApiTests.cs @@ -0,0 +1,18 @@ +namespace SampleApp.Tests +{ + using NUnit.Framework; + using SampleApp.WebApi; + using TestStack.ConventionTests; + using TestStack.ConventionTests.ConventionData; + using TestStack.ConventionTests.Conventions; + + [TestFixture] + public class WebApiTests + { + [Test] + public void WebApiConventions() + { + Convention.Is(new ApiControllerNamingAndBaseClassConvention(), Types.InAssemblyOf()); + } + } +} \ No newline at end of file diff --git a/SampleApp.Tests/WpfTests.cs b/SampleApp.Tests/WpfTests.cs new file mode 100644 index 0000000..914e8b7 --- /dev/null +++ b/SampleApp.Tests/WpfTests.cs @@ -0,0 +1,18 @@ +namespace SampleApp.Tests +{ + using NUnit.Framework; + using SampleApp.Wpf; + using TestStack.ConventionTests; + using TestStack.ConventionTests.ConventionData; + using TestStack.ConventionTests.Conventions; + + [TestFixture] + public class WpfTests + { + [Test] + public void ViewModelCOnventions() + { + Convention.Is(new ViewModelShouldInheritFromINotifyPropertyChanged(), Types.InAssemblyOf()); + } + } +} \ No newline at end of file diff --git a/SampleApp.Tests/__TryItOut__.txt b/SampleApp.Tests/__TryItOut__.txt new file mode 100644 index 0000000..bd1b4be --- /dev/null +++ b/SampleApp.Tests/__TryItOut__.txt @@ -0,0 +1,11 @@ +There are a number of conventions setup in this project, to try convention tests out, simply violate a convention, then run the tests. + +Some examples: + + - Create a class ending in Dto outside the Dto namespace + - Add a non-virtual method to the domain class + - Make the domain class constructor private + - Change the SqlScripts\Script0001.sql file build action to Content (rather than EmbeddedResource) + - Create an API Controller where you incorrectly spell 'Controller' + +These are some of the examples of conventions which are easily put in place with convention tests! \ No newline at end of file diff --git a/SampleApp.Tests/packages.config b/SampleApp.Tests/packages.config new file mode 100644 index 0000000..4db763b --- /dev/null +++ b/SampleApp.Tests/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/SampleApp/Domain/DomainClass.cs b/SampleApp/Domain/DomainClass.cs new file mode 100644 index 0000000..cda5f86 --- /dev/null +++ b/SampleApp/Domain/DomainClass.cs @@ -0,0 +1,11 @@ +namespace SampleApp.Domain +{ + public class DomainClass + { + protected DomainClass() { } + + public virtual string Prop { get; set; } + + public virtual void SomeAction() { } + } +} \ No newline at end of file diff --git a/SampleApp/Dtos/SomeDto.cs b/SampleApp/Dtos/SomeDto.cs new file mode 100644 index 0000000..eaadd1e --- /dev/null +++ b/SampleApp/Dtos/SomeDto.cs @@ -0,0 +1,7 @@ +namespace SampleApp.Dtos +{ + public class SomeDto + { + public string Prop { get; set; } + } +} \ No newline at end of file diff --git a/SampleApp/Mvc/TestController.cs b/SampleApp/Mvc/TestController.cs new file mode 100644 index 0000000..0450f15 --- /dev/null +++ b/SampleApp/Mvc/TestController.cs @@ -0,0 +1,9 @@ +namespace SampleApp.Mvc +{ + using System.Web.Mvc; + + public class TestController : Controller + { + + } +} \ No newline at end of file diff --git a/SampleApp/Properties/Annotations.cs b/SampleApp/Properties/Annotations.cs new file mode 100644 index 0000000..28aba6c --- /dev/null +++ b/SampleApp/Properties/Annotations.cs @@ -0,0 +1,577 @@ +using System; + +#pragma warning disable 1591 +// ReSharper disable UnusedMember.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable IntroduceOptionalParameters.Global +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable InconsistentNaming + +namespace SampleApp.Annotations +{ + /// + /// Indicates that the value of the marked element could be null sometimes, + /// so the check for null is necessary before its usage + /// + /// + /// [CanBeNull] public object Test() { return null; } + /// public void UseTest() { + /// var p = Test(); + /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | + AttributeTargets.Property | AttributeTargets.Delegate | + AttributeTargets.Field, AllowMultiple = false, Inherited = true)] + public sealed class CanBeNullAttribute : Attribute { } + + /// + /// Indicates that the value of the marked element could never be null + /// + /// + /// [NotNull] public object Foo() { + /// return null; // Warning: Possible 'null' assignment + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | + AttributeTargets.Property | AttributeTargets.Delegate | + AttributeTargets.Field, AllowMultiple = false, Inherited = true)] + public sealed class NotNullAttribute : Attribute { } + + /// + /// Indicates that the marked method builds string by format pattern and (optional) arguments. + /// Parameter, which contains format string, should be given in constructor. The format string + /// should be in -like form + /// + /// + /// [StringFormatMethod("message")] + /// public void ShowError(string message, params object[] args) { /* do something */ } + /// public void Foo() { + /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string + /// } + /// + [AttributeUsage( + AttributeTargets.Constructor | AttributeTargets.Method, + AllowMultiple = false, Inherited = true)] + public sealed class StringFormatMethodAttribute : Attribute + { + /// + /// Specifies which parameter of an annotated method should be treated as format-string + /// + public StringFormatMethodAttribute(string formatParameterName) + { + FormatParameterName = formatParameterName; + } + + public string FormatParameterName { get; private set; } + } + + /// + /// Indicates that the function argument should be string literal and match one + /// of the parameters of the caller function. For example, ReSharper annotates + /// the parameter of + /// + /// + /// public void Foo(string param) { + /// if (param == null) + /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol + /// } + /// + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + public sealed class InvokerParameterNameAttribute : Attribute { } + + /// + /// Indicates that the method is contained in a type that implements + /// interface + /// and this method is used to notify that some property value changed + /// + /// + /// The method should be non-static and conform to one of the supported signatures: + /// + /// NotifyChanged(string) + /// NotifyChanged(params string[]) + /// NotifyChanged{T}(Expression{Func{T}}) + /// NotifyChanged{T,U}(Expression{Func{T,U}}) + /// SetProperty{T}(ref T, T, string) + /// + /// + /// + /// public class Foo : INotifyPropertyChanged { + /// public event PropertyChangedEventHandler PropertyChanged; + /// [NotifyPropertyChangedInvocator] + /// protected virtual void NotifyChanged(string propertyName) { ... } + /// + /// private string _name; + /// public string Name { + /// get { return _name; } + /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } + /// } + /// } + /// + /// Examples of generated notifications: + /// + /// NotifyChanged("Property") + /// NotifyChanged(() => Property) + /// NotifyChanged((VM x) => x.Property) + /// SetProperty(ref myField, value, "Property") + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute + { + public NotifyPropertyChangedInvocatorAttribute() { } + public NotifyPropertyChangedInvocatorAttribute(string parameterName) + { + ParameterName = parameterName; + } + + public string ParameterName { get; private set; } + } + + /// + /// Describes dependency between method input and output + /// + /// + ///

Function Definition Table syntax:

+ /// + /// FDT ::= FDTRow [;FDTRow]* + /// FDTRow ::= Input => Output | Output <= Input + /// Input ::= ParameterName: Value [, Input]* + /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} + /// Value ::= true | false | null | notnull | canbenull + /// + /// If method has single input parameter, it's name could be omitted.
+ /// Using halt (or void/nothing, which is the same) + /// for method output means that the methos doesn't return normally.
+ /// canbenull annotation is only applicable for output parameters.
+ /// You can use multiple [ContractAnnotation] for each FDT row, + /// or use single attribute with rows separated by semicolon.
+ ///
+ /// + /// + /// [ContractAnnotation("=> halt")] + /// public void TerminationMethod() + /// + /// + /// [ContractAnnotation("halt <= condition: false")] + /// public void Assert(bool condition, string text) // regular assertion method + /// + /// + /// [ContractAnnotation("s:null => true")] + /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() + /// + /// + /// // A method that returns null if the parameter is null, and not null if the parameter is not null + /// [ContractAnnotation("null => null; notnull => notnull")] + /// public object Transform(object data) + /// + /// + /// [ContractAnnotation("s:null=>false; =>true,result:notnull; =>false, result:null")] + /// public bool TryParse(string s, out Person result) + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public sealed class ContractAnnotationAttribute : Attribute + { + public ContractAnnotationAttribute([NotNull] string contract) + : this(contract, false) { } + + public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) + { + Contract = contract; + ForceFullStates = forceFullStates; + } + + public string Contract { get; private set; } + public bool ForceFullStates { get; private set; } + } + + /// + /// Indicates that marked element should be localized or not + /// + /// + /// [LocalizationRequiredAttribute(true)] + /// public class Foo { + /// private string str = "my string"; // Warning: Localizable string + /// } + /// + [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)] + public sealed class LocalizationRequiredAttribute : Attribute + { + public LocalizationRequiredAttribute() : this(true) { } + public LocalizationRequiredAttribute(bool required) + { + Required = required; + } + + public bool Required { get; private set; } + } + + /// + /// Indicates that the value of the marked type (or its derivatives) + /// cannot be compared using '==' or '!=' operators and Equals() + /// should be used instead. However, using '==' or '!=' for comparison + /// with null is always permitted. + /// + /// + /// [CannotApplyEqualityOperator] + /// class NoEquality { } + /// class UsesNoEquality { + /// public void Test() { + /// var ca1 = new NoEquality(); + /// var ca2 = new NoEquality(); + /// if (ca1 != null) { // OK + /// bool condition = ca1 == ca2; // Warning + /// } + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Interface | AttributeTargets.Class | + AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] + public sealed class CannotApplyEqualityOperatorAttribute : Attribute { } + + /// + /// When applied to a target attribute, specifies a requirement for any type marked + /// with the target attribute to implement or inherit specific type or types. + /// + /// + /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement + /// public class ComponentAttribute : Attribute { } + /// [Component] // ComponentAttribute requires implementing IComponent interface + /// public class MyComponent : IComponent { } + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] + [BaseTypeRequired(typeof(Attribute))] + public sealed class BaseTypeRequiredAttribute : Attribute + { + public BaseTypeRequiredAttribute([NotNull] Type baseType) + { + BaseType = baseType; + } + + [NotNull] public Type BaseType { get; private set; } + } + + /// + /// Indicates that the marked symbol is used implicitly + /// (e.g. via reflection, in external library), so this symbol + /// will not be marked as unused (as well as by other usage inspections) + /// + [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)] + public sealed class UsedImplicitlyAttribute : Attribute + { + public UsedImplicitlyAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) { } + + public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) { } + + public UsedImplicitlyAttribute( + ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + public ImplicitUseKindFlags UseKindFlags { get; private set; } + public ImplicitUseTargetFlags TargetFlags { get; private set; } + } + + /// + /// Should be used on attributes and causes ReSharper + /// to not mark symbols marked with such attributes as unused + /// (as well as by other usage inspections) + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public sealed class MeansImplicitUseAttribute : Attribute + { + public MeansImplicitUseAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) { } + + public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) { } + + public MeansImplicitUseAttribute( + ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; private set; } + [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; private set; } + } + + [Flags] + public enum ImplicitUseKindFlags + { + Default = Access | Assign | InstantiatedWithFixedConstructorSignature, + /// Only entity marked with attribute considered used + Access = 1, + /// Indicates implicit assignment to a member + Assign = 2, + /// + /// Indicates implicit instantiation of a type with fixed constructor signature. + /// That means any unused constructor parameters won't be reported as such. + /// + InstantiatedWithFixedConstructorSignature = 4, + /// Indicates implicit instantiation of a type + InstantiatedNoFixedConstructorSignature = 8, + } + + /// + /// Specify what is considered used implicitly + /// when marked with + /// or + /// + [Flags] + public enum ImplicitUseTargetFlags + { + Default = Itself, + Itself = 1, + /// Members of entity marked with attribute are considered used + Members = 2, + /// Entity marked with attribute and all its members considered used + WithMembers = Itself | Members + } + + /// + /// This attribute is intended to mark publicly available API + /// which should not be removed and so is treated as used + /// + [MeansImplicitUse] + public sealed class PublicAPIAttribute : Attribute + { + public PublicAPIAttribute() { } + public PublicAPIAttribute([NotNull] string comment) + { + Comment = comment; + } + + [NotNull] public string Comment { get; private set; } + } + + /// + /// Tells code analysis engine if the parameter is completely handled + /// when the invoked method is on stack. If the parameter is a delegate, + /// indicates that delegate is executed while the method is executed. + /// If the parameter is an enumerable, indicates that it is enumerated + /// while the method is executed + /// + [AttributeUsage(AttributeTargets.Parameter, Inherited = true)] + public sealed class InstantHandleAttribute : Attribute { } + + /// + /// Indicates that a method does not make any observable state changes. + /// The same as System.Diagnostics.Contracts.PureAttribute + /// + /// + /// [Pure] private int Multiply(int x, int y) { return x * y; } + /// public void Foo() { + /// const int a = 2, b = 2; + /// Multiply(a, b); // Waring: Return value of pure method is not used + /// } + /// + [AttributeUsage(AttributeTargets.Method, Inherited = true)] + public sealed class PureAttribute : Attribute { } + + /// + /// Indicates that a parameter is a path to a file or a folder + /// within a web project. Path can be relative or absolute, + /// starting from web root (~) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public class PathReferenceAttribute : Attribute + { + public PathReferenceAttribute() { } + public PathReferenceAttribute([PathReference] string basePath) + { + BasePath = basePath; + } + + [NotNull] public string BasePath { get; private set; } + } + + // ASP.NET MVC attributes + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC action. If applied to a method, the MVC action name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String) + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcActionAttribute : Attribute + { + public AspMvcActionAttribute() { } + public AspMvcActionAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [NotNull] public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC area. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcAreaAttribute : PathReferenceAttribute + { + public AspMvcAreaAttribute() { } + public AspMvcAreaAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [NotNull] public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that + /// the parameter is an MVC controller. If applied to a method, + /// the MVC controller name is calculated implicitly from the context. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String) + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcControllerAttribute : Attribute + { + public AspMvcControllerAttribute() { } + public AspMvcControllerAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [NotNull] public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(String, String) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcMasterAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(String, Object) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcModelTypeAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that + /// the parameter is an MVC partial view. If applied to a method, + /// the MVC partial view name is calculated implicitly from the context. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String) + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcPartialViewAttribute : PathReferenceAttribute { } + + /// + /// ASP.NET MVC attribute. Allows disabling all inspections + /// for MVC views within a class or a method. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public sealed class AspMvcSupressViewErrorAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcDisplayTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcEditorTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC template. + /// Use this attribute for custom wrappers similar to + /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view. If applied to a method, the MVC view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(Object) + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcViewAttribute : PathReferenceAttribute { } + + /// + /// ASP.NET MVC attribute. When applied to a parameter of an attribute, + /// indicates that this parameter is an MVC action name + /// + /// + /// [ActionName("Foo")] + /// public ActionResult Login(string returnUrl) { + /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK + /// return RedirectToAction("Bar"); // Error: Cannot resolve action + /// } + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] + public sealed class AspMvcActionSelectorAttribute : Attribute { } + + [AttributeUsage( + AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Field, Inherited = true)] + public sealed class HtmlElementAttributesAttribute : Attribute + { + public HtmlElementAttributesAttribute() { } + public HtmlElementAttributesAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] public string Name { get; private set; } + } + + [AttributeUsage( + AttributeTargets.Parameter | AttributeTargets.Field | + AttributeTargets.Property, Inherited = true)] + public sealed class HtmlAttributeValueAttribute : Attribute + { + public HtmlAttributeValueAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] public string Name { get; private set; } + } + + // Razor attributes + + /// + /// Razor attribute. Indicates that a parameter or a method is a Razor section. + /// Use this attribute for custom wrappers similar to + /// System.Web.WebPages.WebPageBase.RenderSection(String) + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, Inherited = true)] + public sealed class RazorSectionAttribute : Attribute { } +} \ No newline at end of file diff --git a/SampleApp/Properties/AssemblyInfo.cs b/SampleApp/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..af8c5c0 --- /dev/null +++ b/SampleApp/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SampleApp")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SampleApp")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("31ee7421-19f6-40b4-9435-d6413bb9319f")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SampleApp/SampleApp.csproj b/SampleApp/SampleApp.csproj new file mode 100644 index 0000000..f740aa2 --- /dev/null +++ b/SampleApp/SampleApp.csproj @@ -0,0 +1,96 @@ + + + + + Debug + AnyCPU + {56467A5A-7DD6-45B3-A84C-144A3C5D0C7A} + Library + Properties + SampleApp + SampleApp + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll + + + + + + + ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll + + + ..\packages\Microsoft.AspNet.WebApi.Client.4.0.30506.0\lib\net40\System.Net.Http.Formatting.dll + + + ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll + + + ..\packages\Microsoft.AspNet.WebApi.Core.4.0.30506.0\lib\net40\System.Web.Http.dll + + + + + + + + + + + + + + + + + + + SomeView.xaml + + + + + + + + + + Designer + + + + + Designer + MSBuild:Compile + + + + + \ No newline at end of file diff --git a/SampleApp/SampleApp.ncrunchproject b/SampleApp/SampleApp.ncrunchproject new file mode 100644 index 0000000..c4410bf --- /dev/null +++ b/SampleApp/SampleApp.ncrunchproject @@ -0,0 +1,22 @@ + + false + false + false + true + false + false + false + false + true + true + false + true + true + 60000 + + + + AutoDetect + STA + x86 + \ No newline at end of file diff --git a/SampleApp/SqlScripts/Script0001.sql b/SampleApp/SqlScripts/Script0001.sql new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/SampleApp/SqlScripts/Script0001.sql @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/SampleApp/WebApi/TestApiController.cs b/SampleApp/WebApi/TestApiController.cs new file mode 100644 index 0000000..0a1a66a --- /dev/null +++ b/SampleApp/WebApi/TestApiController.cs @@ -0,0 +1,9 @@ +namespace SampleApp.WebApi +{ + using System.Web.Http; + + public class TestApiController : ApiController + { + + } +} \ No newline at end of file diff --git a/SampleApp/Wpf/SomeView.xaml b/SampleApp/Wpf/SomeView.xaml new file mode 100644 index 0000000..7724fd9 --- /dev/null +++ b/SampleApp/Wpf/SomeView.xaml @@ -0,0 +1,11 @@ + + + + + diff --git a/SampleApp/Wpf/SomeView.xaml.cs b/SampleApp/Wpf/SomeView.xaml.cs new file mode 100644 index 0000000..55155e6 --- /dev/null +++ b/SampleApp/Wpf/SomeView.xaml.cs @@ -0,0 +1,10 @@ +namespace SampleApp.Wpf +{ + public partial class SomeView + { + public SomeView() + { + InitializeComponent(); + } + } +} diff --git a/SampleApp/Wpf/SomeViewModel.cs b/SampleApp/Wpf/SomeViewModel.cs new file mode 100644 index 0000000..d65b07a --- /dev/null +++ b/SampleApp/Wpf/SomeViewModel.cs @@ -0,0 +1,6 @@ +namespace SampleApp.Wpf +{ + public class SomeViewModel : ViewModelBase + { + } +} diff --git a/SampleApp/Wpf/ViewModelBase.cs b/SampleApp/Wpf/ViewModelBase.cs new file mode 100644 index 0000000..e1024ab --- /dev/null +++ b/SampleApp/Wpf/ViewModelBase.cs @@ -0,0 +1,17 @@ +namespace SampleApp.Wpf +{ + using System.ComponentModel; + using SampleApp.Annotations; + + public class ViewModelBase : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + [NotifyPropertyChangedInvocator] + protected virtual void OnPropertyChanged(string propertyName) + { + var handler = PropertyChanged; + if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); + } + } +} \ No newline at end of file diff --git a/SampleApp/packages.config b/SampleApp/packages.config new file mode 100644 index 0000000..3d90b39 --- /dev/null +++ b/SampleApp/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/TestStack.ConventionTests/ConventionData/AbstractProjectData.cs b/TestStack.ConventionTests/ConventionData/AbstractProjectData.cs index 971779c..48d8483 100644 --- a/TestStack.ConventionTests/ConventionData/AbstractProjectData.cs +++ b/TestStack.ConventionTests/ConventionData/AbstractProjectData.cs @@ -7,11 +7,11 @@ public abstract class AbstractProjectData : IConventionData { - protected AbstractProjectData(Assembly assembly, IProjectProvider projectProvider, IProjectLocator projectLocator) + protected AbstractProjectData(Assembly assembly, IProjectProvider projectProvider = null, IProjectLocator projectLocator = null) { Assembly = assembly; - ProjectProvider = projectProvider; - ProjectLocator = projectLocator; + ProjectProvider = projectProvider ?? new ProjectProvider(); + ProjectLocator = projectLocator ?? new AssemblyProjectLocator(); } public Assembly Assembly { get; private set; } diff --git a/TestStack.ConventionTests/ConventionData/ProjectFiles.cs b/TestStack.ConventionTests/ConventionData/ProjectFiles.cs index 71e45d8..bbaf6a2 100644 --- a/TestStack.ConventionTests/ConventionData/ProjectFiles.cs +++ b/TestStack.ConventionTests/ConventionData/ProjectFiles.cs @@ -7,7 +7,7 @@ public class ProjectFiles : AbstractProjectData { - public ProjectFiles(Assembly assembly, IProjectProvider projectProvider, IProjectLocator projectLocator) + public ProjectFiles(Assembly assembly, IProjectProvider projectProvider = null, IProjectLocator projectLocator = null) : base(assembly, projectProvider, projectLocator) { } diff --git a/TestStack.ConventionTests/ConventionData/ProjectReferences.cs b/TestStack.ConventionTests/ConventionData/ProjectReferences.cs index eb83825..3937a16 100644 --- a/TestStack.ConventionTests/ConventionData/ProjectReferences.cs +++ b/TestStack.ConventionTests/ConventionData/ProjectReferences.cs @@ -9,7 +9,7 @@ public class ProjectReferences : AbstractProjectData { - public ProjectReferences(Assembly assembly, IProjectProvider projectProvider, IProjectLocator projectLocator) + public ProjectReferences(Assembly assembly, IProjectProvider projectProvider = null, IProjectLocator projectLocator = null) : base(assembly, projectProvider, projectLocator) { } diff --git a/TestStack.ConventionTests/Conventions/ViewModelShouldInheritFromINotifyPropertyChanged.cs b/TestStack.ConventionTests/Conventions/ViewModelShouldInheritFromINotifyPropertyChanged.cs new file mode 100644 index 0000000..7358582 --- /dev/null +++ b/TestStack.ConventionTests/Conventions/ViewModelShouldInheritFromINotifyPropertyChanged.cs @@ -0,0 +1,27 @@ +namespace TestStack.ConventionTests.Conventions +{ + using System; + using System.ComponentModel; + using System.Linq; + using TestStack.ConventionTests.ConventionData; + + public class ViewModelShouldInheritFromINotifyPropertyChanged : IConvention + { + readonly string viewModelSuffix; + + public ViewModelShouldInheritFromINotifyPropertyChanged(string viewModelSuffix = "ViewModel") + { + this.viewModelSuffix = viewModelSuffix; + } + + public void Execute(Types data, IConventionResultContext result) + { + var notifyPropertyChanged = typeof (INotifyPropertyChanged); + var failingData = data.TypesToVerify.Where(t => t.Name.EndsWith(viewModelSuffix, StringComparison.InvariantCultureIgnoreCase)) + .Where(t => !notifyPropertyChanged.IsAssignableFrom(t)); + + result.Is("ViewModels (types named *{0}) should inherit from INotifyPropertyChanged", + failingData); + } + } +} \ No newline at end of file diff --git a/TestStack.ConventionTests/TestStack.ConventionTests.csproj b/TestStack.ConventionTests/TestStack.ConventionTests.csproj index c6c359a..0f12072 100644 --- a/TestStack.ConventionTests/TestStack.ConventionTests.csproj +++ b/TestStack.ConventionTests/TestStack.ConventionTests.csproj @@ -60,6 +60,7 @@ + diff --git a/packages/repositories.config b/packages/repositories.config index ec107ca..0b4fda2 100644 --- a/packages/repositories.config +++ b/packages/repositories.config @@ -1,6 +1,8 @@  + +