From 537622e06e21b14b945c43477cdf0bee9a79fd34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Terje=20Sandstr=C3=B8m?= Date: Mon, 24 May 2021 21:17:55 +0200 Subject: [PATCH 1/4] Updated project --- .gitignore | 1 + .runsettings | 1 + NUnit3TestAdapter.ndproj | 13033 +++++++++++++++++++++++++++++++++++++ 3 files changed, 13035 insertions(+) create mode 100644 NUnit3TestAdapter.ndproj diff --git a/.gitignore b/.gitignore index 2ba44ec0..5f6005c2 100644 --- a/.gitignore +++ b/.gitignore @@ -412,3 +412,4 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ /dupl.xml +NDependOut/ diff --git a/.runsettings b/.runsettings index f9a9c17b..9bf92652 100644 --- a/.runsettings +++ b/.runsettings @@ -3,6 +3,7 @@ 0 + types that implement some interfaces. + t.NbInterfacesImplemented == 0 && + + // --> or classes that have sub-classes children. + t.NbChildren == 0 && + + // --> or classes that have a base class + ((t.IsClass && t.DepthOfDeriveFrom("System.Object".AllowNoMatch()) == 1) || + t.IsStructure) && + + // Don't match test classes + !testAttributes.Any(tAttr => t.HasAttribute(tAttr)) && + + // Don't warn about classes instantiated by Dependency Injection frameworks + !t.TypesUsingMe.Any(t1 => t1.TypesUsed.ParentNamespaces().Any(n => n.Name.StartsWithAny( + "Microsoft.Extensions.DependencyInjection", + "Autofac", "Microsoft.Practices.Unity", "Ninject", + "StructureMap", "SimpleInjector", "Castle.Windsor", + "LightInject", "Spring", "Lamar", "AutoMapper" + ))) && + !t.BaseClasses.Any(bc => bc.FullName.StartsWith("AutoMapper")) + +let methodsUsingMe = t.TypesUsingMe.ChildMethods().Where(m => m.IsUsing(t)) + +select new { + t, + methodsUsingMe, + Debt = (1 + methodsUsingMe.Count()).ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// This rule matches classes and structures that are not static, nor generic, +// that doesn't have any instance fields, that doesn't implement any interface +// nor has a base class (different than *System.Object*). +// +// Such class or structure is a *stateless* collection of *pure* functions, +// that doesn't act on any *this* object data. Such collection of *pure* functions +// is better hosted in a **static class**. Doing so simplifies the client code +// that doesn't have to create an object anymore to invoke the *pure* functions. +// + +// +// Declare all methods as *static* and transform the class or structure +// into a *static* class. +// +// By default issues of this rule have a **Low** severity +// because they reflect more an advice than a problem. +//]]> + Non-static classes should be instantiated or turned to static +// ND1207:NonStaticClassesShouldBeInstantiatedOrTurnedToStatic +warnif count > 0 + +let testAttributes = Types + .Where(t => t.IsAttributeClass && t.SimpleName.Contains("Test")).ToArray() + +from t in JustMyCode.Types +where t.IsClass && + //!t.IsPublic && // if you are developing a framework, + // you might not want to match public classes + !t.IsStatic && + !t.IsAbstract && + !t.IsAttributeClass && // Attributes class are never seen as instantiated + + // Don't suggest to turn to static, classes that implement interfaces + t.InterfacesImplemented.Count() == 0 && + + // Find the first constructor of t called + // match t if none of its constructors is called. + t.Constructors.FirstOrDefault(ctor => ctor.NbMethodsCallingMe > 0) == null && + + // Don't warn on Program classes generated by designers + t.SimpleName != "Program" && + + // Types instantiated through remoting infrastructure + !t.DeriveFrom("System.MarshalByRefObject".AllowNoMatch()) && + + // JSON, XML and binary serialized types might not be seen as instantiated. + !t.IsUsing("Newtonsoft.Json".MatchNamespace().AllowNoMatch()) && + !t.HasAttribute("System.Xml.Serialization.XmlRootAttribute".AllowNoMatch()) && + !t.IsUsing("System.Xml.Serialization.XmlElementAttribute".AllowNoMatch()) && + !t.IsUsing("System.Xml.Serialization.XmlAttributeAttribute".AllowNoMatch()) && + !t.HasAttribute ("System.SerializableAttribute".AllowNoMatch()) && + + // Serialized type might never be seen as instantiated. + !t.HasAttribute("System.Runtime.Serialization.DataContractAttribute".AllowNoMatch()) && + !t.IsUsing("System.Runtime.Serialization.DataMemberAttribute".AllowNoMatch()) && + + // ASP.NET Core ViewModel and Repository + !t.SimpleName.EndsWithAny("Model","Repository") && + !t.IsUsing("System.ComponentModel.DataAnnotations".AllowNoMatch().MatchNamespace()) && + + // ASP.NET Classes that are instantiated by the ASP.NET infrastructure. + !t.BaseClasses.Any(bc => bc.ParentNamespace.Name.StartsWithAny("System.Web", "Microsoft.AspNetCore")) && + !(t.Constructors.Count() == 1 && t.Constructors.Single().Name.Contains("(IHostingEnvironment)")) && + + // Entity Framework ModelSnapshot and DbContext and Migration + !t.DeriveFrom("Microsoft.EntityFrameworkCore.Infrastructure.ModelSnapshot".AllowNoMatch()) && + !t.DeriveFrom("Microsoft.EntityFrameworkCore.DbContext".AllowNoMatch()) && + !t.DeriveFrom("Microsoft.EntityFrameworkCore.Migrations.Migration".AllowNoMatch()) && + + // ASP.NET Core Tag Helpers + !t.HasAttribute("Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute".AllowNoMatch()) && + !t.DeriveFrom("HtmlTags.HtmlTagTagHelper".AllowNoMatch()) && + + // UWP Pages + !t.InstanceMethods.WithName("GetBindingConnector(Int32,Object)").Any() && + + // Don't match test classes + !testAttributes.Any(tAttr => t.HasAttribute(tAttr)) && + + // Validtor classes might not be instantiated + !t.SimpleName.EndsWith("Validator") && + + // Don't warn about classes instantiated by Dependency Injection frameworks + !t.TypesUsingMe.Any(t1 => t1.TypesUsed.ParentNamespaces().Any(n => n.Name.StartsWithAny( + "Microsoft.Extensions.DependencyInjection", + "Autofac", "Microsoft.Practices.Unity", "Ninject", + "StructureMap", "SimpleInjector", "Castle.Windsor", + "LightInject", "Spring", "Lamar", "AutoMapper" + ))) && + !t.BaseClasses.Any(bc => bc.FullName.StartsWith("AutoMapper")) && + + // Don't warn if the type is used by some types using some third-party Json related types. + // The type is potentially instantiated by a Json serializer like the GetFromJsonAsync() extension methods. + !t.TypesUsingMe.SelectMany(t1 => t1.TypesUsed) + .Where(t1 => t1.IsThirdParty && t1.SimpleName.Contains("Json")).Any() && + + // Classes that derive from Unity classes are instantiated implicitely by the Unity engine + !t.BaseClasses.Any(bc => bc.ParentNamespace.Name.StartsWithAny("UnityEngine","UnityEditor")) + +select new { + t, + t.Visibility, + Debt = 2.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// Notice that classes only instantiated through reflection, like plug-in root classes +// are matched by this rules. + +// +// If the constructors of a class are never called, the class is +// never instantiated, and should be defined as a *static class*. +// +// However this rule doesn't match instantiation through reflection. +// As a consequence, plug-in root classes, instantiated through reflection +// via *IoC frameworks*, can be *false positives* for this rule. +// +// This rule doesn't match also classes instantiated by the ASP.NET +// infrastructure, ASP.NET view model classes +// and Entity Framework ModelSnapshot, DbContext and Migration classes. +// +// Notice that by default this rule matches also *public* class. +// If you are developing a framework with classes that are intended +// to be instantiated by your clients, just uncomment the line +// *!t.IsPublic*. +// + +// +// First it is important to investigate why the class is never instantiated. +// If the reason is *the class hosts only static methods* then the class +// can be safely declared as *static*. +// +// Others reasons like, *the class is meant to be instantiated via reflection*, +// or *is meant to be instantiated only by client code* should lead to +// adapt this rule code to avoid these matches. +//]]> + Methods should be declared static if possible +// ND1208:MethodsShouldBeDeclaredStaticIfPossible +warnif count > 0 + +let testAttributes = ThirdParty.Types + .Where(t => t.IsAttributeClass && + ( t.SimpleName.Contains("Test") || + t.SimpleName.Contains("Fact") || + t.SimpleName.Contains("SetUp") || + t.SimpleName.Contains("TearDown") ) + ).ToArray() + +from t in JustMyCode.Types.Where(t => + !t.IsStatic && !t.IsInterface && + !t.IsEnumeration && !t.IsDelegate && + !t.IsGeneratedByCompiler && + + // Don't advise to declare Global ASP.NET or ApiController methods as static + !t.SimpleName.EndsWith("Controller") && + !t.DeriveFrom("System.Web.HttpApplication".AllowNoMatch()) && + !t.DeriveFrom("System.Web.Http.ApiController".AllowNoMatch()) && + !t.DeriveFrom("Microsoft.AspNetCore.Mvc.Controller".AllowNoMatch()) && + !t.DeriveFrom("Microsoft.AspNetCore.Mvc.ControllerBase".AllowNoMatch()) && + !t.HasAttribute("Microsoft.AspNetCore.Mvc.ControllerAttribute".AllowNoMatch()) && + + // Don't set as static methods of JSON serialized type + !t.TypesUsed.Any(t1 => t1.IsAttributeClass && t1.ParentNamespace.Name == "Newtonsoft.Json") && + + // Don't set as static methods of XML serialized type + !t.HasAttribute("System.Xml.Serialization.XmlRootAttribute".AllowNoMatch()) && + !t.IsUsing("System.Xml.Serialization.XmlElementAttribute".AllowNoMatch()) && + !t.IsUsing("System.Xml.Serialization.XmlAttributeAttribute".AllowNoMatch()) && + !t.HasAttribute("System.SerializableAttribute".AllowNoMatch()) && + + // Methods of classes that derive from Unity classes are used implicitely by the Unity engine + !t.BaseClasses.Any(bc => bc.ParentNamespace.Name.StartsWithAny("UnityEngine","UnityEditor")) +) + +let methodsThatCanBeMadeStatic = + from m in t.InstanceMethods + + // An instance method can be turned to static if it is not virtual, + // not using the this reference and also, not using + // any of its class or base classes instance fields or instance methods. + where !m.IsAbstract && !m.IsVirtual && + !m.AccessThis && !m.IsExplicitInterfaceImpl && + !m.IsProtected && // Protected method access doesn't match well with static methods + + // Don't warn about not yet implemented methods. + !m.CreateA("System.NotImplementedException".AllowNoMatch()) && + + // Optimization: Using FirstOrDefault() avoid to check all members, + // as soon as one member is found + // we know the method m cannot be made static. + m.MembersUsed.FirstOrDefault( + mUsed => !mUsed.IsStatic && + (mUsed.ParentType == t || + t.DeriveFrom(mUsed.ParentType)) + ) == null + + // Don't match test methods + && !testAttributes.Any(tAttr => m.HasAttribute(tAttr)) + select m + +from m in methodsThatCanBeMadeStatic +let staticFieldsUsed = m.ParentType.StaticFields.UsedBy(m).Where(f => !f.IsGeneratedByCompiler) +let methodsCallingMe = m.MethodsCallingMe + +// All callers of the method must be in JustMyCode, +// else having a method declared as static would break the call from the code generated +// like when a WPF Connect() method is binding a method to an event. +where methodsCallingMe.All(m1 => JustMyCode.Contains(m1)) + +select new { + m, + staticFieldsUsed, + methodsCallingMe, + Debt = (1 + methodsCallingMe.Count())*30.ToSeconds().ToDebt(), + Severity = Severity.Medium +} + +// +// When an instance method can be *safely* declared as static you should declare it as static. +// +// Whenever you write a method, you fulfill a contract in a given scope. +// The narrower the scope is, the smaller the chance is that you write a bug. +// +// When a method is static, you can't access non-static members; hence, your scope is +// narrower. So, if you don't need and will never need (even in subclasses) instance +// fields to fulfill your contract, why give access to these fields to your method? +// Declaring the method static in this case will let the compiler check that you +// don't use members that you do not intend to use. +// +// Declaring a method as static if possible is also good practice because clients can +// tell from the method signature that calling the method can't alter the object's state. +// +// Doing so, is also a micro performance optimization, since a static method is a +// bit cheaper to invoke than an instance method, because the *this* reference* +// doesn't need anymore to be passed. +// +// Notice that if a matched method is a handler, bound to an event through code +// generated by a designer, declaring it as static might break the designer +// generated code, if the generated code use the *this* invocation syntax, +// (like *this.Method()*). +// + +// +// Declare matched methods as static. +// +// Since such method doesn't use any instance fields and methods of its type and +// base-types, you should consider if it makes sense, to move such a method +// to a static utility class. +//]]> + Constructor should not call a virtual method +// ND1209:ConstructorShouldNotCallAVirtualMethod +warnif count > 0 + +from t in JustMyCode.Types where + t.IsClass && + !t.IsGeneratedByCompiler && + !t.IsSealed + +from ctor in t.Constructors +let virtualMethodsCalled = + from mCalled in ctor.MethodsCalled + where mCalled.IsVirtual && !mCalled.IsFinal && + // Only take care of just-my-code virtual methods called + JustMyCode.Contains(mCalled) && + ( mCalled.ParentType == t || + (t.DeriveFrom(mCalled.ParentType) && + // Don't accept Object methods since they can be called + // from another reference than the 'this' reference. + mCalled.ParentType.FullName != "System.Object") + ) + select mCalled +where virtualMethodsCalled.Count() > 0 + +select new { + ctor , + virtualMethodsCalled, + // If there is no derived type, it might be + // an opportunity to mark t as sealed. + t.DerivedTypes, + Debt = ((virtualMethodsCalled.Count())*6).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule matches constructors of a non-sealed class that call one or +// several virtual methods. +// +// When an object written in C# is constructed, what happens is that constructors +// run in order from the base class to the most derived class. +// +// Also objects do not change type as they are constructed, but start out as +// the most derived type, with the method table being for the most derived type. +// This means that virtual method calls always run on the most derived type, +// even when calls are made from the constructor. +// +// When you combine these two facts you are left with the problem that if you +// make a virtual method call in a constructor, and it is not the most derived +// type in its inheritance hierarchy, then it will be called on a class whose +// constructor has not been run, and therefore may not be in a suitable state +// to have that method called. +// +// Hence this situation makes the class *fragile to derive from*. +// + +// +// Violations reported can be solved by re-designing object initialisation +// or by declaring the parent class as *sealed*, if possible. +// +]]> + Avoid the Singleton pattern +// ND1210:AvoidTheSingletonPattern +warnif count > 0 +from t in Application.Types +where !t.IsStatic && !t.IsAbstract && (t.IsClass || t.IsStructure) + +// All ctors of a singleton are private +where t.Constructors.Where(ctor => !ctor.IsPrivate).Count() == 0 + +// Require mutable instance fields to be shared across the several clients of the single object. +let mutableInstanceFields = t.InstanceFields.Where(f => !f.IsImmutable) +where mutableInstanceFields.Any() + +// A singleton contains one or several static fields of its parent type, +// or of an interface implented by its parent type, +// to reference the unique instance +let staticFieldInstances = t.StaticFields.WithFieldTypeIn(t.InterfacesImplemented.Concat(t)) +where staticFieldInstances.Count() == 1 + +let staticFieldInstance = staticFieldInstances.Single() +let methodsUsingField = staticFieldInstance.MethodsUsingMe +let methodsUsingField2 = methodsUsingField.Concat(methodsUsingField.SelectMany(m => m.MethodsCallingMe)) + +select new { + t, + staticFieldInstance, + methodsUsingField2, + mutableInstanceFields , + Debt = (3*methodsUsingField2.Count()).ToMinutes().ToDebt(), + AnnualInterest = (10+methodsUsingField2.Count()).ToMinutes().ToAnnualInterest() +} + +// +// The *singleton pattern* consists in enforcing that a class has just +// a single instance: http://en.wikipedia.org/wiki/Singleton_pattern +// At first glance, this pattern looks appealing, it is simple to implement, +// it adresses a common situation, and as a consequence it is widely used. +// +// However, we discourage you from using singleton classes because experience +// shows that **singleton often results in less testable and less maintainable code**. +// Singleton is *by-design*, not testable. Each unit test should use their own objects +// while singleton forces multiple unit-tests to use the same instance object. +// +// Also the singleton static *GetInstance()* method allows *magic* access to that +// single object and its state from wherever developers want! This potentially +// attractive facility unfortunatly ends up into *unorganized*/*messy* code that +// will require effort to be refactored. +// +// Notice that this rule matches only singleton types with **mutable** instance fields +// because singleton pitfalls result from anarchical access and modification +// of instance data. +// +// More details available in these discussions: +// https://blog.ndepend.com/the-proper-usages-of-the-keyword-static-in-c/ +// http://adamschepis.com/blog/2011/05/02/im-adam-and-im-a-recovering-singleton-addict/ +// + +// +// This rule matches *the classic syntax of singletons*, where one +// static field hold the single instance of the parent class. We underline that +// *the problem is this particular syntax*, that plays against testability. +// The problem is not the fact that a single instance of the class lives +// at runtime. +// +// Hence to fix matches fo this rule, creates the single instance +// at the startup of the program, and pass it to all classes and methods +// that need to access it. +// +// If multiple singletons are identified, they actually form together a +// *program execution context*. Such context can be unified in a unique +// singleton context. Doing so will make it easier to propagate the +// context across the various program units. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 3 minutes per method relying on the singleton. +// It is not rare that hundreds of methods rely on the singleton +// and that it takes hours to get rid of a singleton, refactoring +// the way just explained above. +// +// The severity of each singleton issue is **Critical** because as +// explained, using a the singleton pattern can really prevent the +// whole program to be testable. +//]]> + Don't assign static fields from instance methods +// ND1211:DontAssignStaticFieldsFromInstanceMethods +warnif count > 0 +from f in Application.Fields where + f.IsStatic && + !f.IsLiteral && + !f.IsInitOnly && + !f.IsGeneratedByCompiler && + // Contract API define such a insideContractEvaluation static field + f.Name != "insideContractEvaluation" +let assignedBy = f.MethodsAssigningMe.Where(m => !m.IsStatic) +where assignedBy .Count() > 0 +select new { + f, + assignedBy, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// Assigning static fields from instance methods leads to +// poorly maintainable and non-thread-safe code. +// +// More discussion on the topic can be found here: +// https://blog.ndepend.com/the-proper-usages-of-the-keyword-static-in-c/ +// + +// +// If the *static* field is just assigned once in the program +// lifetime, make sure to declare it as *readonly* and assign +// it inline, or from the static constructor. +// +// In *Object-Oriented-Programming* the natural artifact +// to hold states that can be modified is **instance fields**. +// +// Hence to fix violations of this rule, make sure to +// hold assignable states through *instance* fields, not +// through *static* fields. +//]]> + Avoid empty interfaces +// ND1212:AvoidEmptyInterfaces +warnif count > 0 from t in JustMyCode.Types where + t.IsInterface && + t.NbMethods == 0 && + !t.InterfacesImplemented.Any() +select new { + t, + t.TypesThatImplementMe, + Debt = (10 + 3*t.TypesThatImplementMe.Count()).ToMinutes().ToDebt(), + Severity = t.TypesThatImplementMe.Any() ? Severity.Medium : Severity.Low +} + +// +// Interfaces define members that provide a behavior or usage contract. +// The functionality that is described by the interface +// can be adopted by any type, regardless of where the type +// appears in the inheritance hierarchy. +// A type implements an interface by providing implementations +// for the members of the interface. +// An empty interface does not define any members. +// Therefore, it does not define a contract that can be implemented. +// +// If your design includes empty interfaces that types +// are expected to implement, you are probably using an interface +// as a marker or a way to identify a group of types. +// If this identification will occur at run time, +// the correct way to accomplish this is to use a custom attribute. +// Use the presence or absence of the attribute, +// or the properties of the attribute, to identify the target types. +// If the identification must occur at compile time, +// then it is acceptable to use an empty interface. +// +// Note that if an interface is empty but implements at least one +// other interface, it won't be matched by this rule. +// Such interface can be considered as not empty, +// since implementing it means that sub-interfaces members +// must be implemented. +// + +// +// Remove the interface or add members to it. +// If the empty interface is being used to label a set of types, +// replace the interface with a custom attribute. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 10 minutes to discard an empty interface plus +// 3 minutes per type implementing an empty interface. +//]]> + Avoid types initialization cycles +// ND1213:AvoidTypesInitializationCycles +warnif count > 0 + +// Types initialization cycle can only happen between types of an assembly. +from assembly in Application.Assemblies + +let cctorSuspects = assembly.ChildMethods.Where( + m => m.IsClassConstructor && + // Optimization: types involved in a type cycle necessarily don't have type level. + m.ParentType.Level == null) + +where cctorSuspects.Count() > 1 +let typesSuspects = cctorSuspects.ParentTypes().ToHashSetEx() + +// +// dicoTmp associates to each type suspect T, a set of types from typesSuspects +// that contains at least a method or a field used directly or indirectly by the cctor of T. +// +let dicoTmp = cctorSuspects.ToDictionary( + cctor => cctor.ParentType, + cctor => ((IMember)cctor).ToEnumerable().FillIterative( + members => from m in members + from mUsed in (m is IMethod) ? (m as IMethod).MembersUsed : new IMember[0] + where mUsed.ParentAssembly == assembly + select mUsed) + .DefinitionDomain + .Select(m => m.ParentType) // Don't need .Distinct() here, because of ToHashSetEx() below. + .Except(cctor.ParentType) + .Intersect(typesSuspects) + .ToHashSetEx() +) + +// +// dico associates to each type suspect T, the set of types initialized (directly or indirectly) +// by the initialization of T. This second step is needed, because if a cctor of a type T1 +// calls a member of a type T2, not only the cctor of T1 triggers the initialization of T2, +// but also it triggers the initialization of all types that are initialized by T2 initialization. +// +let dico = typesSuspects.Where(t => dicoTmp[t].Count() > 0).ToDictionary( + typeSuspect => typeSuspect, + typeSuspect => typeSuspect.ToEnumerable().FillIterative( + types => from t in types + from tUsed in dicoTmp[t] + select tUsed) + .DefinitionDomain + .Except(typeSuspect) + .ToHashSetEx() +) + + +// +// Now that dico is prepared, detect the cctor cycles +// +from t in dico.Keys + + // Thanks to the work done to build dico, it is now pretty easy + // to spot types involved in an initialization cyle with t! + let usersAndUseds = from tTmp in dico[t] + where dico.ContainsKey(tTmp) && dico[tTmp].Contains(t) + select tTmp + where usersAndUseds.Count() > 0 + + // Here we've found type(s) both using and used by the suspect type. + // A cycle involving the type t is found! + // v2017.3.2: don't call Append() as an extension method else ambiguous syntax error + // with the new extension method in .NET Fx v4.7.1 / .NET Standard 2.0: System.Linq.Enumerable.Append() + let typeInitCycle = ExtensionMethodsEnumerable.Append(usersAndUseds,t) + + + // Compute methodsCalled and fieldsUsed, useful to explore + // how a cctor involved in a type initialization cycle, triggers other type initialization. + let methodsCalledDepth = assembly.ChildMethods.DepthOfIsUsedBy(t.ClassConstructor) + let fieldsUsedDepth = assembly.ChildFields.DepthOfIsUsedBy(t.ClassConstructor) + + let methodsCalled = methodsCalledDepth.DefinitionDomain.OrderBy(m => methodsCalledDepth[m]).ToArray() + let fieldsUsed = fieldsUsedDepth.DefinitionDomain.OrderBy(f => fieldsUsedDepth[f]).ToArray() + +// Use the tick box to: Group cctors methods By parent types +select new { + t.ClassConstructor, + cctorsCycle= typeInitCycle.Select(tTmp => tTmp.ClassConstructor), + + // methodsCalled and fieldsUsed are members used directly and indirectly by the cctor. + // Export these members to the dependency graph (right click the cell Export/Append … to the Graph) + // and see how the cctor trigger the initialization of other types + methodsCalled, + fieldsUsed, + Debt = (20+10*typeInitCycle.Count()).ToMinutes().ToDebt(), + Severity = Severity.Critical +} + +// +// The *class constructor* (also called *static constructor*, and named *cctor* in IL code) +// of a type, if any, is executed by the CLR at runtime, the first time the type is used. +// A *cctor* doesn't need to be explicitly declared in C# or VB.NET, to exist in compiled IL code. +// Having a static field inline initialization is enough to have +// the *cctor* implicitly declared in the parent class or structure. +// +// If the *cctor* of a type *t1* is using the type *t2* and if the *cctor* of *t2* is using *t1*, +// some type initialization unexpected and hard-to-diagnose buggy behavior can occur. +// Such a cyclic chain of initialization is not necessarily limited to two types +// and can embrace *N* types in the general case. +// More information on types initialization cycles can be found here: +// http://codeblog.jonskeet.uk/2012/04/07/type-initializer-circular-dependencies/ +// +// The present code rule enumerates types initialization cycles. +// Some *false positives* can appear if some lambda expressions are defined +// in *cctors* or in methods called by *cctors*. In such situation, this rule +// considers these lambda expressions as executed at type initialization time, +// while it is not necessarily the case. +// + +// +// Types initialization cycles create confusion and unexpected behaviors. +// If several states hold by several classes must be initialized during the first +// access of any of those classes, a better design option is to create a dedicated +// class whose responsibility is to initialize and hold all these states. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 20 minutes per cycle plus 10 minutes per type class constructor +// involved in the cycle. +//]]> + + + Avoid custom delegates +// ND1300:AvoidCustomDelegates + +warnif count > 0 +from t in JustMyCode.Types where t.IsDelegate + +let invokeMethod = (from m in t.Methods where m.SimpleName == "Invoke" select m).Single() +let signature1 = invokeMethod.Name.Substring( + invokeMethod.SimpleName.Length, + invokeMethod.Name.Length - invokeMethod.SimpleName.Length) + +// 'ref' and 'out' parameters cannot be supported +where !signature1.Contains("&") + +let signature2 = signature1.Replace("(","<").Replace(")",">") +let signature3 = signature2 == "<>" ? "" : signature2 +let resultTypeName = invokeMethod.ReturnType == null ? "????" : + invokeMethod.ReturnType.FullName == "System.Void" ? "" : + invokeMethod.ReturnType.Name +let replaceWith = + resultTypeName == "Boolean" && invokeMethod.NbParameters == 1 ? + "Predicate" + signature3 : resultTypeName == "" ? + "Action" + signature3 : invokeMethod.NbParameters ==0 ? + "Func<" + resultTypeName + ">" : + "Func" + signature3.Replace(">", "," + resultTypeName + ">") + +let methodsUser = t.TypesUsingMe.ChildMethods().Where(m => m.IsUsing(t)) + +select new { + t, + replaceWith, + methodsUser, + Debt = (5 + 3*methodsUser.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// Generic delegates sould be preferred over custom delegates. +// Generic delegates are: +// +// • *Action<…>* to represent any method with *void* return type. +// +// • *Func<…>* to represent any method with a return type. The last +// generic argument is the return type of the prototyped methods. +// +// • *Predicate* to represent any method that takes an instance +// of *T* and that returns a *boolean*. +// +// • Expression<…> that represents function definitions that can be +// compiled and subsequently invoked at runtime but can also be +// serialized and passed to remote processes. +// +// Thanks to generic delegates, not only the code using these custom +// delegates will become clearer, but you'll be relieved from the +// maintenance of these delegate types. +// +// Notice that delegates that are consumed by *DllImport* extern methods +// must not be converted, else this could provoke marshalling issues. +// + +// +// Remove custom delegates and replace them with generic +// delegates shown in the **replaceWith** column. +// +// The estimated Debt, which means the effort to fix such issue, +// is 5 minutes per custom delegates plus 3 minutes per method +// using such custom delegate. +//]]> + Types with disposable instance fields must be disposable +// ND1301:TypesWithDisposableInstanceFieldsMustBeDisposable +warnif count > 0 + +// Several IDisposable types can be found if several .NET profiles are referenced. +let iDisposables = ThirdParty.Types.WithFullName("System.IDisposable") +where iDisposables.Any() // in case the code base doesn't use at all System.IDisposable + +from t in JustMyCode.Types.Except( + JustMyCode.Types.ThatImplementAny(iDisposables) + // Don't match ASP.NET types like Page, MasterPage or Control + .Union(JustMyCode.Types.Where(t => t.BaseClasses.Any(bc => bc.ParentNamespace.Name.StartsWith("System.Web.UI"))))) +where !t.IsGeneratedByCompiler + +let instanceFieldsDisposable = + t.InstanceFields.Where(f => f.FieldType != null && + f.FieldType.InterfacesImplemented.Intersect(iDisposables).Any()) + +where instanceFieldsDisposable.Any() + +// Don't warn for types that implement the dispose pattern with a Dispose(bool) method. +where t.Methods.FirstOrDefault(m => m.Name == "Dispose(Boolean)") == null + +let methodsThatCallsCtor = t.Constructors.SelectMany(ctor => ctor.MethodsCallingMe).Distinct() + +select new { + t, + instanceFieldsDisposable, + methodsThatCallsCtor, + Debt = (5 + 2*instanceFieldsDisposable.Count() + + 4*methodsThatCallsCtor.Count()).ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns when a class declares and implements an instance field that +// is a *System.IDisposable* type and the class does not implement *IDisposable*. +// +// A class implements the *IDisposable* interface to dispose of unmanaged resources +// that it owns. An instance field that is an *IDisposable* type indicates that +// the field owns an unmanaged resource. A class that declares an *IDisposable* +// field indirectly owns an unmanaged resource and should implement the +// *IDisposable* interface. If the class does not directly own any unmanaged +// resources, it should not implement a finalizer. +// +// This rules might report false positive in case the lifetime of the disposable +// objects referenced, is longer than the lifetime of the object that hold the +// disposable references. +// + +// +// To fix a violation of this rule, implement *IDisposable* and from the +// *IDisposable.Dispose()* method call the *Dispose()* method of the field(s). +// +// Then for each method calling a constructor of the type, make +// sure that the *Dispose()* method is called on all objects created. +// +// The estimated Debt, which means the effort to fix such issue, +// is 5 minutes per type matched plus 2 minutes per disposable instance field +// plus 4 minutes per method calling a constructor of the type. +//]]> + Disposable types with unmanaged resources should declare finalizer +// ND1302:DisposableTypesWithUnmanagedResourcesShouldDeclareFinalizer + +// This default rule is disabled by default, +// see in the rule description (below) why. +// warnif count > 0 + +// Several IDisposable type can be found if several .NET Fx are referenced. +let iDisposables = ThirdParty.Types.WithFullName("System.IDisposable") +where iDisposables.Any() // in case the code base doesn't use at all System.IDisposable + +let disposableTypes = Application.Types.ThatImplementAny(iDisposables) +let unmanagedResourcesFields = disposableTypes.ChildFields().Where(f => + !f.IsStatic && + f.FieldType != null && + f.FieldType.FullName.EqualsAny( + "System.IntPtr", + "System.UIntPtr", + "System.Runtime.InteropServices.HandleRef")).ToHashSetEx() +let disposableTypesWithUnmanagedResource = unmanagedResourcesFields.ParentTypes() + +from t in disposableTypesWithUnmanagedResource +where !t.HasFinalizer +let unmanagedResourcesTypeFields = unmanagedResourcesFields.Intersect(t.InstanceFields) +select new { + t, + unmanagedResourcesTypeFields, + //Debt = 10.ToMinutes().ToDebt(), + //Severity = Severity.Critical +} + +// +//A type that implements *System.IDisposable*, +//and has fields that suggest the use of unmanaged resources, +//does not implement a finalizer as described by *Object.Finalize()*. +//A violation of this rule is reported +//if the disposable type contains fields of the following types: +// +// • *System.IntPtr* +// +// • *System.UIntPtr* +// +// • *System.Runtime.InteropServices.HandleRef* +// +// Notice that this default rule is disabled by default, +// because it typically reports *false positive* for classes +// that just hold some references to managed resources, +// without the responsibility to dispose them. +// +// To enable this rule just uncomment *warnif count > 0*. +// + +// +//To fix a violation of this rule, +//implement a finalizer that calls your *Dispose()* method. +//]]> + Methods that create disposable object(s) and that don't call Dispose() +// ND1303:MethodsThatCreateDisposableObjectAndThatDontCallDispose + +// Uncomment this to transform this code query into a code rule. +// warnif count > 0 + +// Several IDisposable types can be found if several .NET Fx are referenced. +let iDisposables = ThirdParty.Types.WithFullName("System.IDisposable") +where iDisposables.Any() // in case the code base doesn't use at all System.IDisposable + +// Build sequences of disposableTypes and disposeMethods +let disposableTypes = Types.ThatImplementAny(iDisposables).Concat(iDisposables) +let disposeMethods = disposableTypes.ChildMethods().WithName("Dispose()").ToHashSetEx() + + +// -> You can refine this code query by assigning to disposableTypesToLookAfter something like: +// disposableTypes.WithFullNameIn("Namespace.TypeName1", "Namespace.TypeName2", ...) +let disposableTypesToLookAfter = disposableTypes + + +// -> You can refine this code query by assigning to methodsToLookAfter something like: +// Application.Assemblies.WithNameLike("Asm").ChildMethods() +let methodsToLookAfter = Application.Methods + + +// Enumerate methods that create any disposable type, without calling Dispose() +from m in methodsToLookAfter.ThatCreateAny(disposableTypesToLookAfter ) + +where !m.MethodsCalled.Intersect(disposeMethods).Any() +select new { + m, + disposableObjectsCreated = disposableTypes.Where(t => m.CreateA(t)), + m.MethodsCalled, + //Debt = 10.ToMinutes().ToDebt(), + //Severity = Severity.Low +} + +// +// This code query enumerates methods that create one or several disposable object(s), +// without calling any Dispose() method. +// +// This code query is not a code rule because it is acceptable to do so, +// as long as disposable objects are disposed somewhere else. +// +// This code query is designed to be be easily refactored +// to look after only specific disposable types, or specific caller methods. +// +// You can then refactor this code query to adapt it to your needs and transform it into a code rule. +//]]> + Classes that are candidate to be turned into structures +// ND1304:ClassesThatAreCandidateToBeTurnedIntoStructures + +warnif count > 0 +from t in JustMyCode.Types where + t.IsClass && + !t.IsGeneratedByCompiler && + !t.IsStatic && + !t.IsGeneric && + + t.NbChildren == 0 && // Must not have children + + t.Constructors.All(c => c.NbParameters > 0) && // Must not have parameterless ctor, struct cannot have custom parameterless ctor + + t.IsImmutable && // Structures should be immutable type. + + // Must have fields and all fields must be of value-type + t.InstanceFields.Count() > 0 && + t.InstanceFields.All(f => f.FieldType != null) && + t.InstanceFields.All(f => f.FieldType.IsStructure || f.FieldType.IsEnumeration) && + + // Must not implement interfaces to avoid boxing mismatch + // when structures implements interfaces. + t.InterfacesImplemented.Count() == 0 && + + // Must derive directly from System.Object + t.DepthOfDeriveFrom("System.Object".AllowNoMatch()) == 1 && + + // Must not be a serializable class because a structure should be immutable + // and serialized types are mutable. + !t.TypesUsed.Any(t1 => t1.IsAttributeClass && t1.ParentNamespace.Name == "Newtonsoft.Json") && + !t.HasAttribute("System.Xml.Serialization.XmlRootAttribute".AllowNoMatch()) && + !t.IsUsing("System.Xml.Serialization.XmlElementAttribute".AllowNoMatch()) && + !t.IsUsing("System.Xml.Serialization.XmlAttributeAttribute".AllowNoMatch()) && + !t.HasAttribute("System.Runtime.Serialization.DataContractAttribute".AllowNoMatch()) && + !t.IsUsing("System.Runtime.Serialization.DataMemberAttribute".AllowNoMatch()) && + !t.HasAttribute("System.SerializableAttribute".AllowNoMatch()) && + + // ASP.NET Core ViewModel and Repository + !t.SimpleName.EndsWithAny("Model","Repository") && + !t.IsUsing("System.ComponentModel.DataAnnotations".AllowNoMatch().MatchNamespace()) && + + // Must not be an entry point class like 'Program' + !t.Methods.Any(m => m.IsEntryPoint) + + // && t.IsSealed <-- You might want to add this condition + // to restraint the set. + // && !t.IsPubliclyVisible <-- You might want to add this condition if + // you are developping a framework with classes + // that are intended to be sub-classed by + // your clients. + let methodsUser = t.TypesUsingMe.ChildMethods().Where(m => m.IsUsing(t)) + +select new { + t, + t.SizeOfInst, + t.InstanceFields, + methodsUser, + Debt = (5 + 1*methodsUser.Count()).ToMinutes().ToDebt(), + Severity = Severity.Info +} + +// +// *Int32*, *Double*, *Char* or *Boolean* are structures and not classes. +// Structures are particularly suited to implement **lightweight values**. +// Hence a class is candidate to be turned into a structure +// when its instances are *lightweight values*. +// +// This is a matter of *performance*. It is expected that a program +// works with plenty of *short lived lightweight values*. +// In such situation, the advantage of using *struct* instead of +// *class*, (in other words, the advantage of using *values* instead +// of *objects*), is that *values* are not managed by the garbage collector. +// This means that values are cheaper to deal with. +// +// This rule matches classes that looks like being *lightweight values*. +// The characterization of such class is: +// +// • It has instance fields. +// +// • All instance fields are typed with value-types (primitive, structure or enumeration) +// +// • It is immutable (the value of its instance fields cannot be modified once the constructor ended). +// +// • It implements no interfaces. +// +// • It has no parameterless construtor. +// +// • It is not generic. +// +// • It has no derived classes. +// +// • It derives directly from *System.Object*. +// +// • ASP.NETCore ViewModel (its name ends with *Model*) and Repository +// +// This rule doesn't take account if instances of matched +// classes are numerous *short-lived* objects. +// These criterions are just indications. Only you can decide if it is +// *performance wise* to transform a class into a structure. +// +// A related case-study of using *class* or *struct* for *Tuple<…>* generic +// types can be found here: +// http://stackoverflow.com/questions/2410710/why-is-the-new-tuple-type-in-net-4-0-a-reference-type-class-and-not-a-value-t +// + +// +// Just use the keyword *struct* instead of the keyword *class*. +// +// **CAUTION:** Before applying this rule, make sure to understand +// the **deep implications** of transforming a class into a structure. +// http://msdn.microsoft.com/en-us/library/aa664471(v=vs.71).aspx +// +// The estimated Debt, which means the effort to fix such issue, +// is 5 minutes per class matched plus one minute per method +// using such class transformed into a structure. +//]]> + Avoid namespaces with few types +// ND1305:AvoidNamespacesWithFewTypes + +warnif count > 0 + +// Common infrastructure namespace names not matched by the rule. +// Complete this list to your need. +let infraNamespaceNames = new HashSet() { + "Services","Exceptions","Logging", "Domain", + "Identity", "Migrations", "Controllers", + "Specifications", "Interfaces", "Components", + "Bus", "Models", "EventHandlers", "Mappings", + "ViewModels", "ViewComponents", "Notifications", + "Configurations", "Extensions", "Events", + "Context", "Data", "EventSourcing", "Repository", + "IoC", "Manage", "Commands", "CommandHandlers", + "Validations", "EventStoreSQL", "Authorization", + "Formatters", "Xml", "Json", "Enums", + "Abstractions", "BaseClasses", "Settings", + "Helpers", "Validators", "Entities", "Pages" +} + +// Only warn for namespaces with few types declared in assemblies with more than 15 types. +// This way the rule avoids matching namespace with few types in assemblies with few types. +from a in JustMyCode.Assemblies +where a.NbTypes >= 15 + +from n in a.ChildNamespaces +where n.Name.Length > 0 // Don't match anonymous namespaces + && !infraNamespaceNames.Contains(n.SimpleName) // Don't match common infrastructure namespaces + && !n.ParentNamespaces.Any(pn => infraNamespaceNames.Contains(pn.SimpleName)) // Don't match children of common infrastructure namespaces + && n.Name != n.ParentAssembly.Name // Don't warn on namespace named as assembly to avoid warning on new VS projects +let types = n.ChildTypes.Where(t => !t.IsGeneratedByCompiler).ToArray() +where + types.Length > 0 && // Don't match namespaces that contain only types GeneratedByCompiler + types.Length < 5 && + // Only match namespaces that have all types in JustMyCode + types.All(t => JustMyCode.Contains(t)) + orderby types.Length ascending +select new { + n, + types, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// This rule warns about namespaces other than the global namespace +// that contain less than five types. +// +// Make sure that each of your namespaces has a logical organization +// and that a valid reason exists to put types in a sparsely +// populated namespace. +// +// Namespaces should contain types that are used together in most +// scenarios. When their applications are mutually exclusive, +// types should be located in separate namespaces. For example, +// the *System.Web.UI* namespace contains types that are used +// in Web applications, and the *System.Windows.Forms* namespace +// contains types that are used in Windows-based applications. +// Even though both namespaces have types that control aspects +// of the user interface, these types are not designed for +// use in the same application. Therefore, they are located in +// separate namespaces. +// +// Careful namespace organization can also be helpful because +// it increases the discoverability of a feature. By examining the +// namespace hierarchy, library consumers should be able to locate +// the types that implement a feature. +// +// Notice that this rule source code contains a list of common +// infrastructure namespace names that you can complete. +// Namespaces with ending name component in this list are not matched. +// + +// +// To fix a violation of this rule, try to combine namespaces +// that contain just a few types into a single namespace. +//]]> + Nested types should not be visible +// ND1306:NestedTypesShouldNotBeVisible + +warnif count > 0 from t in JustMyCode.Types where + t.IsNested && + !t.IsGeneratedByCompiler && + !t.IsPrivate && + + // It is accepted that non-private nested types are declared insider Razor page classes. + !t.OutterTypes.Any(ot => ot.DeriveFrom("Microsoft.AspNetCore.Mvc.RazorPages.PageModel".AllowNoMatch()) || + ot.DeriveFrom("Microsoft.AspNetCore.Components.ComponentBase".AllowNoMatch())) + +let typesUser = t.TypesUsingMe.Where(t1 => t1 != t.ParentType && t1.ParentType != t.ParentType) +select new { + t, + t.Visibility, + typesUser, + Debt = (2 + 4*typesUser.Count()).ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns about nested types not declared as private. +// +// A nested type is a type declared within the scope of another +// type. Nested types are useful for encapsulating private +// implementation details of the containing type. Used +// for this purpose, nested types should not be externally visible. +// +// Do not use externally visible nested types for logical +// grouping or to avoid name collisions; instead use namespaces. +// +// Nested types include the notion of member accessibility, +// which some programmers do not understand clearly. +// +// Nested types declared as protected can be used in subclasses. +// + +// +// If you do not intend the nested type to be externally visible, +// change the type's accessibility. +// +// Otherwise, remove the nested type from its parent and make it +// *non-nested*. +// +// If the purpose of the nesting is to group some nested types, +// use a namespace to create the hierarchy instead. +// +// The estimated Debt, which means the effort to fix such issue, +// is 2 minutes per nested type plus 4 minutes per outter type +// using such nesting type. +//]]> + Declare types in namespaces +// ND1307:DeclareTypesInNamespaces + +warnif count > 0 from n in Application.Namespaces where + // If an anonymous namespace can be found, + // it means that it contains types outside of namespaces. + n.Name == "" + + // Eliminate anonymous namespaces that contains + // only notmycode types (like generated types). + let childTypes = n.ChildTypes.Where(t => JustMyCode.Contains(t)) + where childTypes.Count() > 0 +select new { + n, + childTypes, + n.NbLinesOfCode, + Debt = 2*childTypes.Count().ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// Types are declared within namespaces to prevent name collisions, +// and as a way of organizing related types in an object hierarchy. +// +// Types outside any named namespace are in a *global +// namespace* that cannot be referenced in code. +// +// The *global namespace* has no name, hence it is qualified as +// being the *anonymous namespace*. +// +// This rule warns about *anonymous namespaces*. +// + +// +// To fix a violation of this rule, +// declare all types of all anonymous +// namespaces in some named namespaces. +//]]> + Empty static constructor can be discarded +// ND1308:EmptyStaticConstructorCanBeDiscarded + +warnif count > 0 from m in JustMyCode.Methods where + m.IsClassConstructor && + m.NbLinesOfCode == 0 +select new { + m, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// The *class constructor* (also called *static constructor*, and named *cctor* in IL code) +// of a type, if any, is executed by the CLR at runtime, just before the first time the type is used. +// +// This rule warns about the declarations of *static constructors* +// that don't contain any lines of code. Such *cctors* are useless +// and can be safely removed. +// + +// +// Remove matched empty *static constructors*. +//]]> + Instances size shouldn't be too big +// ND1309:InstancesSizeShouldntBeTooBig + +warnif count > 0 from t in JustMyCode.Types where + t.SizeOfInst > 128 && + + // You might want to restrict this rule only on structure, since the cost of copying instance data at each method call might be prohibitive + // t.IsStructure && + + // Discard types that represent WPF, WindowsForm and ASP.NET forms and controls and EntityFramwork classes. + t.BaseClasses.All(bc => !bc.ParentNamespace.Name.StartsWithAny( + "System.Windows", "System.Web.UI", "System.Web", "Microsoft.EntityFramework", + // Discard types related to these namespaces that typically require large instances size + "System.ComponentModel", "System.Xml", + // Just add more component vendors here if needed + "DevExpress")) + + orderby t.SizeOfInst descending +select new { + t, + t.SizeOfInst, + t.InstanceFields, + t.BaseClasses, + Debt = t.SizeOfInst.Linear(128,10, 2048, 120).ToMinutes().ToDebt(), + + // The annual interest varies linearly from interests for severity Medium for 64 bytes per instance + // to twice interests for severity High for 2048 bytes per instance + AnnualInterest = (t.SizeOfInst.Linear(128, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes, + 2048, 2*(Severity.High.AnnualInterestThreshold().Value.TotalMinutes)) + )*(t.IsStructure ? 10 : 1) // Multiply interest by 10 for structures + .ToMinutes().ToAnnualInterest() + +} + +// +// Types where *SizeOfInst > 128* might degrade performance +// if many instances are created at runtime. +// They can also be hard to maintain. +// +// Notice that a class with a large *SizeOfInst* value +// doesn't necessarily have a lot of instance fields. +// It might derive from a class with a large *SizeOfInst* value. +// +// This query doesn't match types that represent WPF +// and WindowsForm forms and controls nor Entity Framework +// special classes. +// +// Some other namespaces like *System.ComponentModel* or *System.Xml* +// have base classes that typically imply large instances size +// so this rule doesn't match these situations. +// +// This rule doesn't match custom *DevExpress* component +// and it is easy to modify this rule ro append other component vendors +// to avoid false positives. +// +// See the definition of the *SizeOfInst* metric here +// https://www.ndepend.com/docs/code-metrics#SizeOfInst +// + +// +// A type with a large *SizeOfInst* value hold *directly* +// a lot of data. Typically, you can group this data into +// smaller types that can then be composed. +// +// The estimated Debt, which means the effort to fix such issue, +// varies linearly from severity **Medium** for 128 bytes per instance +// to twice interests for severity **High** for 2048 bytes per instance. +// +// The estimated annual interest of issues of this rule is 10 times higher +// for structures, because large structures have a significant performance cost. +// Indeed, each time such structure *value* is passed as a method parameter +// it gets copied to a new local variable +// (note that the word *value* is more appropriate than the word *instance* for structures). +// For this reason, such structure should be declared as class. +//]]> + Attribute classes should be sealed +// ND1310:AttributeClassesShouldBeSealed + +warnif count > 0 from t in Application.Types where + t.IsAttributeClass && + !t.IsSealed && + !t.IsAbstract && + t.IsPublic +select new { + t, + t.NbLinesOfCode, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// The .NET Framework class library provides methods +// for retrieving custom attributes. By default, +// these methods search the attribute inheritance +// hierarchy; for example +// *System.Attribute.GetCustomAttribute()* +// searches for the specified attribute type, or any +// attribute type that extends the specified attribute +// type. +// +// Sealing the attribute eliminates the search +// through the inheritance hierarchy, and can improve +// performance. +// + +// +// To fix a violation of this rule, seal the attribute +// type or make it abstract. +//]]> + Don't use obsolete types, methods or fields +// ND1311:DontUseObsoleteTypesMethodsOrFields + +warnif count > 0 +let obsoleteTypes = Types.Where(t => t.IsObsolete) +let obsoleteMethods = Methods.Where(m => m.IsObsolete).ToHashSetEx() +let obsoleteFields = Fields.Where(f => f.IsObsolete) + +from m in JustMyCode.Methods.UsingAny(obsoleteTypes).Union( + JustMyCode.Methods.UsingAny(obsoleteMethods)).Union( + JustMyCode.Methods.UsingAny(obsoleteFields)) +let obsoleteTypesUsed = obsoleteTypes.UsedBy(m) + +// Optimization: MethodsCalled + Intersect() is faster than using obsoleteMethods.UsedBy() +let obsoleteMethodsUsed = m.MethodsCalled.Intersect(obsoleteMethods) +let obsoleteFieldsUsed = obsoleteFields.UsedBy(m) + +let obsoleteUsage = obsoleteTypesUsed.Cast().Concat(obsoleteMethodsUsed).Concat(obsoleteFieldsUsed) + +select new { + m, + obsoleteUsage, + Debt = (5*obsoleteUsage.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// The attribute *System.ObsoleteAttribute* is used to tag +// types, methods or fields of an API that clients shouldn't +// use because these code elements will be removed sooner +// or later. +// +// This rule warns about methods that use a type, a method +// or a field, tagged with *System.ObsoleteAttribute*. +// + +// +// Typically when a code element is tagged with +// *System.ObsoleteAttribute*, a *workaround message* +// is provided to clients. +// +// This *workaround message* will tell you what to do +// to avoid using the obsolete code element. +// +// The estimated Debt, which means the effort to fix such issue, +// is 5 minutes per type, method or field used. +// +// Issues of this rule have a severity **High** +// because it is important to not rely anymore on obsolete code. +//]]> + Do implement methods that throw NotImplementedException +// ND1312:DoImplementMethodsThatThrowNotImplementedException + +warnif count > 0 +from m in JustMyCode.Methods +where m.CreateA("System.NotImplementedException".AllowNoMatch()) +select new { + m, + m.NbLinesOfCode, + Debt = (m.NbLinesOfCode == 1 ? 10 : 3).ToMinutes().ToDebt(), + Severity = m.NbLinesOfCode == 1 ? Severity.High : Severity.Medium +} + +// +// The exception *NotImplementedException* is used to declare +// a method *stub* that can be invoked, and defer the +// development of the method implementation. +// +// This exception is especially useful when doing **TDD** +// (*Test Driven Development*) when tests are written first. +// This way tests fail until the implementation is written. +// +// Hence using *NotImplementedException* is a *temporary* +// facility, and before releasing, will come a time when +// this exception shouldn't be used anywhere in code. +// +// *NotImplementedException* should not be used permanently +// to mean something like *this method should be overriden* +// or *this implementation doesn't support this facility*. +// Artefact like *abstract method* or *abstract class* should +// be used instead, to favor a *compile time* error over a +// *run-time* error. +// +// This rule warns about method still using +// *NotImplementedException*. +// + +// +// Investigate why *NotImplementedException* is still +// thrown. +// +// Such issue has a **High** severity if the method code +// consists only in throwing *NotImplementedException*. +// Such situation means either that the method should be +// implemented, either that what should be a *compile time* +// error is a *run-time* error *by-design*, +// and this is not good design. Sometime this situation +// also pinpoints a method stub that can be safely removed. +// +// If *NotImplementedException* is thrown from a method +// with significant logic, the severity is considered as +// **Medium**, because often the fix consists in throwing +// another exception type, like **InvalidOperationException**. +//]]> + Override equals and operator equals on value types +// ND1313:OverrideEqualsAndOperatorEqualsOnValueTypes + +warnif count > 0 +from t in JustMyCode.Types where + t.IsStructure && + t.InstanceFields.Count() > 0 +let equalsMethod = t.InstanceMethods.Where(m0 => m0.Name == "Equals(Object)").SingleOrDefault() +where equalsMethod == null +select new { + t, + t.InstanceFields, + Debt = (15 + 2*t.InstanceFields.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// For value types, the inherited implementation of *Equals()* uses +// the Reflection library, and compares the contents of all instances +// fields. Reflection is computationally expensive, and comparing +// every field for equality might be unnecessary. +// +// If you expect users to compare or sort instances, or use them +// as hash table keys, your value type should implement *Equals()*. +// In C# and VB.NET, you should also provide an implementation of +// *GetHashCode()* and of the equality and inequality operators. +// + +// +// To fix a violation of this rule, provide an implementation +// of *Equals()* and *GetHashCode()* and implement the equality +// and inequality operators. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 15 minutes plus 2 minutes per instance field. +//]]> + Boxing/unboxing should be avoided +// ND1314:BoxingUnboxingShouldBeAvoided +warnif count > 0 from m in JustMyCode.Methods where + m.IsUsingBoxing || + m.IsUsingUnboxing +select new { + m, + m.NbLinesOfCode, + m.IsUsingBoxing, + m.IsUsingUnboxing, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// *Boxing* is the process of converting a value type to the type +// *object* or to any interface type implemented by this value +// type. When the CLR boxes a value type, it wraps the value +// inside a *System.Object* and stores it on the managed heap. +// +// *Unboxing* extracts the value type from the object. Boxing +// is implicit; unboxing is explicit. +// +// The concept of boxing and unboxing underlies the C# unified +// view of the type system in which a value of any type can +// be treated as an object. More about *boxing* and *unboxing* +// here: https://msdn.microsoft.com/en-us/library/yz2be5wk.aspx +// +// **This rule has been disabled by default** to avoid noise in +// issues found by the NDepend default rule set. If boxing/unboxing +// is important to your team, just re-activate this rule. +// + +// +// Thanks to .NET generic, and especially thanks to +// generic collections, *boxing* and *unboxing* should +// be rarely used. Hence in most situations the code can +// be refactored to avoid relying on *boxing* and *unboxing*. +// See for example: +// http://stackoverflow.com/questions/4403055/boxing-unboxing-and-generics +// +// With a performance profiler, indentify methods that consume +// a lot of CPU time. If such method uses *boxing* or +// *unboxing*, especially in a **loop**, make sure to refactor it. +// +// By default issues of this rule have a **Low** severity +// because they reflect more an advice than a problem. +//]]> + + + Avoid namespaces mutually dependent +// ND1400:AvoidNamespacesMutuallyDependent + +warnif count > 0 + +// Optimization: restraint application assemblies set +// If some namespaces are mutually dependent +// - They must be declared in the same assembly +// - The parent assembly must ContainsNamespaceDependencyCycle +from assembly in Application.Assemblies.Where(a => a.ContainsNamespaceDependencyCycle != null && a.ContainsNamespaceDependencyCycle.Value) + +// hashset is used to avoid reporting both A <-> B and B <-> A +let hashset = new HashSet() + +// Optimization: restraint namespaces set +let namespacesSuspect = assembly.ChildNamespaces.Where( + // If a namespace doesn't have a Level value, it must be in a dependency cycle + // or it must be using directly or indirectly a dependency cycle. + n => n.Level == null && + // Also require the namespace to be JustMyCode to avoid generated namespaces like My / My.Resources in VB.NET + JustMyCode.Contains(n)) + +from nA in namespacesSuspect + +// Select namespaces mutually dependent with nA +let unused = hashset.Add(nA) // Populate hashset +let namespacesMutuallyDependentWith_nA = nA.NamespacesUsed.Using(nA) + .Except(hashset) // <-- avoid reporting both A <-> B and B <-> A +where namespacesMutuallyDependentWith_nA.Count() > 0 + +from nB in namespacesMutuallyDependentWith_nA + +// nA and nB are mutually dependent +// Infer which one is low level and which one is high level, +// for that we need to compute the coupling from A to B +// and from B to A in terms of number of types and methods +// usages involved in the coupling. +let typesOfBUsedByA = nB.ChildTypes.UsedBy(nA).ToArray() // Enumerate once +let couplingA2B = + typesOfBUsedByA.Sum(t => t.TypesUsingMe.Count(t1 => t1.ParentNamespace == nA)) + + typesOfBUsedByA.ChildMethods().Sum(m => m.MethodsCallingMe.Count(m1 => m1.ParentNamespace == nA)) + +let typesOfAUsedByB = nA.ChildTypes.UsedBy(nB).ToArray() // Enumerate once +let couplingB2A = + typesOfAUsedByB.Sum(t => t.TypesUsingMe.Count(t1 => t1.ParentNamespace == nB)) + + typesOfAUsedByB.ChildMethods().Sum(m => m.MethodsCallingMe.Count(m1 => m1.ParentNamespace == nB)) + +// The lowLevelNamespace is inferred from the fact that +// [coupling lowLevel -> highLevel] is lower than [coupling highLevel -> lowLevel] +let lowLevelNamespace = (couplingA2B < couplingB2A) ? nA : nB +let highLevelNamespace = (lowLevelNamespace == nA) ? nB : nA + +let highLevelTypesUsed = (lowLevelNamespace == nA) ? typesOfBUsedByA : typesOfAUsedByB +let lowLevelTypesUser = lowLevelNamespace.ChildTypes.UsingAny(highLevelTypesUsed) + +let lowLevelTypesMethodsUser = lowLevelTypesUser.Cast() + .Concat(lowLevelTypesUser.ChildMethods().Using(highLevelNamespace)) + .ToArray() // Enumerate once + +// Make the rule works also when lines of code is not available +let lowLevelNamespaceLoc = lowLevelNamespace.NbLinesOfCode ?? (lowLevelNamespace.NbILInstructions / 7) +let highLevelNamespaceLoc = highLevelNamespace.NbLinesOfCode ?? (highLevelNamespace.NbILInstructions / 7) + +let annualInterestPerIssue = + // Such issue has at least a Severity.Medium, never a Severity.Low + Math.Max(Severity.Medium.AnnualInterestThreshold().Value.TotalSeconds, + ((3600 + lowLevelNamespaceLoc + highLevelNamespaceLoc) / Math.Max(1,lowLevelTypesMethodsUser.Length)).Value) + .ToSeconds().ToAnnualInterest() + +// Select in details types and methods involved in the coupling lowLevelNamespace using highLevelNamespace +from tmCulprit in lowLevelTypesMethodsUser +let used = (tmCulprit.IsType ? tmCulprit.AsType.TypesUsed.Where(t => t.ParentNamespace == highLevelNamespace) : + tmCulprit.AsMethod.MembersUsed.Where(m => m.ParentNamespace == highLevelNamespace)) + .ToArray() // Enumerate once + +select new { + tmCulprit, + shouldntUse = used, + becauseNamespace = lowLevelNamespace, + shouldntUseNamespace = highLevelNamespace, + Debt = used.Length.Linear(1, 15, 10, 60).ToMinutes().ToDebt(), + AnnualInterest = annualInterestPerIssue + +} +// +// This rule lists types and methods from a low-level namespace +// that use types and methods from higher-level namespace. +// +// The pair of low and high level namespaces is made of two +// namespaces that use each other. +// +// For each pair of namespaces, to infer which one is low-level +// and which one is high-level, the rule computes the two coupling +// [from A to B] and [from B to A] in terms of number of types, +// methods and fields involved in the coupling. Typically +// the coupling from low-level to high-level namespace is significantly +// lower than the other legitimate coupling. +// +// Following this rule is useful to avoid **namespaces dependency +// cycles**. This will get the code architecture close to a +// *layered architecture*, where *low-level* code is not allowed +// to use *high-level* code. +// +// In other words, abiding by this rule will help significantly +// getting rid of what is often called **spaghetti code: +// Entangled code that is not properly layered and structured**. +// +// More on this in our white books relative to partitioning code. +// https://www.ndepend.com/docs/white-books +// + +// +// Refactor the code to make sure that **the low-level namespace +// doesn't use the high-level namespace**. +// +// The rule lists in detail which low-level types and methods +// shouldn't use which high-level types and methods. The refactoring +// patterns that help getting rid of each listed dependency include: +// +// • Moving one or several types from the *low-level* namespaces +// to the *high-level* one, or do the opposite. +// +// • Use *Inversion of Control (IoC)*: +// http://en.wikipedia.org/wiki/Inversion_of_control +// This consists in creating new interfaces in the +// *low-level* namespace, implemented by classes +// in the *high-level* namespace. This way *low-level* +// code can consume *high-level* code through interfaces, +// without using directly *high-level* implementations. +// Interfaces can be passed to *low-level* code through +// the *high-level* namespace code, or through even +// higher-level code. In related documentations +// you can see these interfaces named as *callbacks*, +// and the overall pattern is also known as +// *Dependency Injection (DI)*: +// http://en.wikipedia.org/wiki/Dependency_injection +// +// That rule might not be applicable for frameworks +// that present public namespaces mutually dependent. +// In such situation the cost to break the API can be +// higher than the cost to let the code entangled. +// +// - +// +// The estimated **Debt**, which means the effort to fix such issue +// to make sure that the first namespace doesn't rely anymore +// on the second one, depends on the number of types and methods used. +// +// Because both namespace are now forming a *super-component* +// that cannot be partitioned in smaller components, the cost to +// unfix each issue is proportional to the size of this super-component. +// As a consequence, the estimated **Annual Interest**, which means +// the annual cost to let both namespaces mutually dependend, is equal +// to an hour plus a number of minutes proportional to the size +// (in lines of code) of both namespaces. The obtained *Annual Interest* +// value is then divided by the number of detailled issues listed. +// +// Often the estimated *Annual Interest* for each listed issue +// is higher than the *Debt*, which means that leaving such issue +// unfixed for a year costs more than taking the time to fix issue once. +// +// -- +// +// To explore the coupling between the two namespaces mutually +// dependent: +// +// 1) from the *becauseNamespace right-click menu* choose +// *Copy to Matrix Columns* to export this low-level namespace +// to the horizontal header of the dependency matrix. +// +// 2) from the *shouldntUseNamespace right-click menu* choose +// *Copy to Matrix Rows* to export this high-level namespace to +// the vertical header of the dependency matrix. +// +// 3) double-click the black matrix cell (it is black because of +// the mutual dependency). +// +// 4) in the matrix command bar, click the button: +// *Remove empty Row(s) and Column(s)*. +// +// At this point, the dependency matrix shows types involved +// into the coupling. +// +// • Blue cells represent types from low-level namespace using types +// from high-level namespace +// +// • Green cells represent types from high-level namespace using +// types from low-level namespace +// +// • Black cells represent types from low-level and high-level +// namespaces that use each other. +// +// There are more green cells than blue and black cells because +// green cell represents correct coupling from high-level to low-level. +// **The goal is to eliminate incorrect dependencies represented by +// blue and black cells.** +// + ]]> + Avoid namespaces dependency cycles +// ND1401:AvoidNamespacesDependencyCycles + +warnif count > 0 + +// Optimization: restraint application assemblies set +// If some namespaces are mutually dependent +// - They must be declared in the same assembly +// - The parent assembly must ContainsNamespaceDependencyCycle +from assembly in Application.Assemblies + .Where(a => a.ContainsNamespaceDependencyCycle != null && + a.ContainsNamespaceDependencyCycle.Value) + +// Optimization: restraint namespaces set +let namespacesSuspect = assembly.ChildNamespaces.Where(n => + // A namespace involved in a cycle necessarily have a null Level. + n.Level == null && + // Also require the namespace to be JustMyCode to avoid generated namespaces like My / My.Resources in VB.NET + JustMyCode.Contains(n)) + +// hashset is used to avoid iterating again on namespaces already caught in a cycle. +let hashset = new HashSet() + + +from suspect in namespacesSuspect + // By commenting in this line, the query matches all namespaces involved in a cycle. + where !hashset.Contains(suspect) + + // Define 2 code metrics + // • Namespaces depth of is using indirectly the suspect namespace. + // • Namespaces depth of is used by the suspect namespace indirectly. + // Note: for direct usage the depth is equal to 1. + let namespacesUserDepth = namespacesSuspect.DepthOfIsUsing(suspect) + let namespacesUsedDepth = namespacesSuspect.DepthOfIsUsedBy(suspect) + + // Select namespaces that are both using and used by namespaceSuspect + let usersAndUsed = from n in namespacesSuspect where + namespacesUserDepth[n] > 0 && + namespacesUsedDepth[n] > 0 + select n + + where usersAndUsed.Count() > 0 + + // Here we've found namespace(s) both using and used by the suspect namespace. + // A cycle involving the suspect namespace is found! + // v2017.3.2: don't call Append() as an extension method else ambiguous syntax error + // with the new extension method in .NET Fx v4.7.1 / .NET Standard 2.0: System.Linq.Enumerable.Append() + let cycle = ExtensionMethodsEnumerable.Append(usersAndUsed,suspect) + + // Fill hashset with namespaces in the cycle. + // .ToArray() is needed to force the iterating process. + let unused1 = (from n in cycle let unused2 = hashset.Add(n) select n).ToArray() + +select new { + suspect, + cycle, + Debt = 120.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule lists all *application namespace dependency cycles*. +// Each row shows a different cycle, indexed with one of the namespace entangled +// in the cycle. +// +// To browse a cycle on the dependency graph or the dependency matrix, right click +// a cycle cell and export the matched namespaces to the dependency graph or matrix. +// +// In the matrix, dependency cycles are represented with red squares and black cells. +// To easily browse dependency cycles, the dependency matrix comes with an option: +// *Display Direct and Indirect Dependencies* +// +// Read our white books relative to partitioning code, +// to know more about namespaces dependency cycles, and why avoiding them +// is a *simple yet efficient* solution to clean the architecture of a code base. +// https://www.ndepend.com/docs/white-books +// + +// +// Removing first pairs of *mutually dependent namespaces* will eliminate +// most *namespaces dependency cycles*. This is why it is recommended +// focusing on matches of the default rule +// **Avoid namespaces mutually dependent** before dealing +// with the present rule. +// +// Once solving all *mutually dependent namespaces*, remaining cycles +// matched by the present rule necessarily involve 3 or more namespaces +// like in: *A is using B is using C is using A*. +// Such cycle can be broken by identifying which namespace should +// be at the *lower-level*. For example if B should be at the +// *lower-level*, then it means C should be at the *higher-level* +// and to break the cycle, you just have to remove the dependency +// from B to C, with a pattern described in the *HowToFix* section +// of the rule *Avoid namespaces mutually dependent*. +// +// The estimated Debt, which means the effort to fix such issue, +// doesn't depend on the cycle length. First because fixing the rule +// **Avoid namespaces mutually dependent** will fix most cycle reported +// here, second because even a long cycle can be broken by removing +// a few dependency. +//]]> + Avoid partitioning the code base through many small library Assemblies +// ND1402:AvoidPartitioningTheCodeBaseThroughManySmallLibraryAssemblies + +warnif count > 10 +from a in Application.Assemblies where + // Don't match ASP.NET Core generated assemblies + // whose name is suffixed with '.Views' + !a.Name.ToLower().EndsWith(".views") && + ( a.NbLinesOfCode < 1000 || + a.NbILInstructions < 7000 ) && + a.FilePath.FileExtension.ToLower() == ".dll" +select new { + a, + a.NbLinesOfCode, + a.NbILInstructions, + Debt = 40.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// Each .NET Assembly compiled represents one or several physical file(s). +// Having too many library .NET Assemblies is a symptom of +// considering **physical** .NET Assemblies as **logical** components. +// +// We advise having less, and bigger, .NET Assemblies +// and using the concept of namespaces to define logical components. +// Benefits are: +// +// • Faster compilation time. +// +// • Faster startup time for your program. +// +// • Easier deployment thanks to less files to manage. +// +// • If you are developing a Framework, +// less .NET assemblies to reference and manage for your clients. +// + +// +// Consider using the *physical* concept of assemblies for physical needs +// only. +// +// Our white book about **Partitioning code base through .NET assemblies +// and Visual Studio projects** explains in details valid and invalid +// reasons to use assemblies. +// Download it here: +// https://www.ndepend.com/Res/NDependWhiteBook_Assembly.pdf +//]]> + UI layer shouldn't use directly DB types +// ND1403:UILayerShouldntUseDirectlyDBTypes + +warnif count > 0 + +// UI layer is made of types using a UI framework +let uiTypes = Application.Types.UsingAny(Assemblies.WithNameIn("PresentationFramework", "System.Windows", "System.Windows.Forms", "System.Web")) + +// You can easily customize this part to define what are DB types. +let dbTypes = ThirdParty.Assemblies.WithNameIn("System.Data", "EntityFramework", "NHibernate").ChildTypes() + // Ideally even DataSet and associated, usage should be forbidden from UI layer: + // http://stackoverflow.com/questions/1708690/is-list-better-than-dataset-for-ui-layer-in-asp-net + .Except(ThirdParty.Types.WithNameIn("DataSet", "DataTable", "DataRow")) + +from uiType in uiTypes.UsingAny(dbTypes) +let dbTypesUsed = dbTypes.Intersect(uiType.TypesUsed) + +let dbTypesAndMembersUsed = dbTypesUsed.Union(dbTypesUsed.ChildMembers().UsedBy(uiType)) + +// Per defaut this rule estimates a technical debt +// proportional to the coupling between the UI and DB types. +let couplingPerUIType = 2 + + uiType.Methods.UsingAny(dbTypesUsed).Count() + + dbTypesAndMembersUsed.Count() + +select new { + uiType, + dbTypesAndMembersUsed, + Debt = (4 * couplingPerUIType).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is more a *sample rule to adapt to your need*, +// than a rigid rule you should abide by. It shows how to +// define code layers in a rule and how to be warned about +// layers dependencies violations. +// +// This rule first defines the UI layer and the DB framework +// layer. Second it checks if any UI layer type is using +// directly any DB framework layer type. +// +// • The **DB framework layer** is defined as the set of *third-party* +// types in the framework *ADO.NET*, *EntityFramework*, +// *NHibernate* types, that the application is consuming. +// It is easy to append and suppress any DB framework. +// +// • The UI layer (**User Interface Layer**) is defined as the +// set of types that use *WPF*, *Windows Form*, *ASP.NET*. +// +// *UI using directly DB frameworks* is generally considered +// as *poor design* because DB frameworks accesses should be +// a concept hidden to UI, encapsulated into a **dedicated +// Data Access Layer (DAL)**. +// +// Notice that per defaut this rule estimates a technical debt +// proportional to the coupling between the UI and DB types. +// + +// +// This rule lists precisely which UI type uses which +// DB framework type. Instead of fixing matches one by one, +// first imagine how DB framework accesses could be +// encapsulated into a dedicated layer. +// +]]> + UI layer shouldn't use directly DAL layer +// ND1404:UILayerShouldntUseDirectlyDALLayer + +warnif count > 0 + +// UI layer is made of types using a UI framework +let uiTypes = Application.Types.UsingAny(Assemblies.WithNameIn("PresentationFramework", "System.Windows", "System.Windows.Forms", "System.Web")) + +// Exclude commonly used DataSet and associated, from ADO.Net types +// You can easily customize this part to define what are DB types. +let dbTypes = ThirdParty.Assemblies.WithNameIn("System.Data", "EntityFramework", "NHibernate").ChildTypes() + .Except(ThirdParty.Types.WithNameIn("DataSet", "DataTable", "DataRow")) + +// DAL layer is made of types using a DB framework +// .ToHashSetEx() results to faster execution of dalTypes.Intersect(uiType.TypesIUse). +let dalTypes = Application.Types.UsingAny(dbTypes).ToHashSetEx() + +from uiType in uiTypes.UsingAny(dalTypes) +let dalTypesUsed = dalTypes.Intersect(uiType.TypesUsed) + +let dalTypesAndMembersUsed = dalTypesUsed.Union(dalTypesUsed.ChildMembers().UsedBy(uiType)) + +// Per defaut this rule estimates a technical debt +// proportional to the coupling between the UI with the DAL layer. +let couplingPerUIType = 2 + + uiType.Methods.UsingAny(dalTypesUsed).Count() + + dalTypesAndMembersUsed.Count() + +select new { + uiType, + // if dalTypesUsed is empty, it means that the uiType is part of the DAL + dalTypesAndMembersUsed, + Debt = (4 * couplingPerUIType).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is more a *sample rule to adapt to your need*, +// than a rigid rule you should abide by. It shows how to +// define code layers in a rule and how to be warned about +// layers dependencies violations. +// +// This rule first defines the UI layer and the DAL layer. +// Second it checks if any UI layer type is using directly +// any DAL layer type. +// +// • The DB layer (the DAL, **Data Access Layer**) is defined as +// the set of types of the application that use *ADO.NET*, +// *EntityFramework*, *NHibernate* types. It is easy to append +// and suppress any DB framework. +// +// • The UI layer (**User Interface Layer**) is defined as the +// set of types that use *WPF*, *Windows Form*, *ASP.NET*. +// +// *UI using directly DAL* is generally considered as *poor +// design* because DAL accesses should be a concept +// hidden to UI, encapsulated into an **intermediary domain +// logic**. +// +// Notice that per defaut this rule estimates a technical debt +// proportional to the coupling between the UI with the DAL layer. +// + +// +// This rule lists precisely which UI type uses which DAL type. +// +// More about this particular design topic here: +// http://www.kenneth-truyers.net/2013/05/12/the-n-layer-myth-and-basic-dependency-injection/ +// +]]> + Assemblies with poor cohesion (RelationalCohesion) +// ND1405:AssembliesWithPoorRelationalCohesion + +warnif count > 0 from a in Application.Assemblies + +// Build the types list on which we want to check cohesion +// This is the assembly 'a' type, minus enumeration +// and types generated by the compiler. +let types = a.ChildTypes.Where( + t => !t.IsGeneratedByCompiler && + !t.IsEnumeration && + JustMyCode.Contains(t)) + // Absolutly need ToHashet() to have fast Intersect() calls below. + .ToHashSetEx() + +// Relational Cohesion metrics is relevant only if there are enough types +where types.LongCount()> 20 + +// R is the total number of relationship between types of the assemblies. +let R = types.Sum(t => t.TypesUsed.Intersect(types).Count()) + +// Relational Cohesion formula +let relationalCohesion = (double)R / types.Count +where + + (relationalCohesion < 1.5 || + relationalCohesion > 4.0) +select new { + a, + a.ChildTypes, + relationalCohesion, + a.RelationalCohesion, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// This rule computes the *Relational Cohesion* metric for +// the application assemblies, and warns about wrong values. +// +// The *Relational Cohesion* for an assembly, is the total number +// of relationship between types of the assemblies, divided +// by the number of types. In other words it is the average +// number of types in the assembly used by a type in the assembly. +// +// As classes inside an assembly should be strongly related, +// the cohesion should be high. On the other hand, a value +// which is too high may indicate over-coupling. A good range +// for *Relational Cohesion* is **1.5 to 4.0**. +// +// Notice that assemblies with less than 20 types are ignored. +// + +// +// Matches of this present rule might reveal either assemblies +// with specific coding constraints (like code generated that +// have particular structure) either issues in design. +// +// In the second case, large refactoring can be planned +// not to respect this rule in particular, but to increase +// the overall design and code maintainability. +// +// The severity of issues of this rule is **Low** because +// the code metric *Relational Cohesion* is an information +// about the code structure state but **is not actionable**, +// it doesn't tell precisely what to do obtain a better score. +// +// Fixing actionable issues of others **Architecture** and +// **Code Smells** default rules will necessarily increase +// the *Relational Cohesion* scores. +//]]> + Namespaces with poor cohesion (RelationalCohesion) +// ND1406:NamespacesWithPoorRelationalCohesion + +warnif count > 0 from n in Application.Namespaces + +// Build the types list on which we want to check cohesion +// This is the namespace children types, minus enumerations +// and types generated by the compiler. +let types = n.ChildTypes.Where( + t => JustMyCode.Contains(t) && !t.IsEnumeration) + // Absolutly need ToHashet() to have fast Intersect() calls below. + .ToHashSetEx() + +// Relational Cohesion metrics is relevant only if there are enough types +where types.LongCount() > 20 + +// R is the total number of relationship between types of the namespaces. +let R = types.Sum(t => t.TypesUsed.Intersect(types).Count()) + +// Relational Cohesion formula +let relationalCohesion = (double)R / types.Count +where + (relationalCohesion < 1.5 || + relationalCohesion > 4.0) +select new { + n, + n.ChildTypes, + relationalCohesion, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// This rule computes the *Relational Cohesion* metric for +// the application namespaces, and warns about wrong values. +// +// The *Relational Cohesion* for a namespace, is the total number +// of relationship between types of the namespaces, divided +// by the number of types. In other words it is the average +// number of types in the namespace used by a type in the namespace. +// +// As classes inside a namespace should be strongly related, +// the cohesion should be high. On the other hand, a value +// which is too high may indicate over-coupling. A good range +// for *Relational Cohesion* is **1.5 to 4.0**. +// +// Notice that namespaces with less than 20 types are ignored. +// + +// +// Matches of this present rule might reveal either namespaces +// with specific coding constraints (like code generated that +// have particular structure) either issues in design. +// +// In the second case, refactoring sessions can be planned +// to increase the overall design and code maintainability. +// +// You can get an overview of class coupling for a +// matched namespace by exporting the *ChildTypes* to the graph. +// (Right click the *ChildTypes* cells) +// +// The severity of issues of this rule is **Low** because +// the code metric *Relational Cohesion* is an information +// about the code structure state but **is not actionable**, +// it doesn't tell precisely what to do obtain a better score. +// +// Fixing actionable issues of others **Architecture** and +// **Code Smells** default rules will necessarily increase +// the *Relational Cohesion* scores. +//]]> + Assemblies that don't satisfy the Abstractness/Instability principle +// ND1407:AssembliesThatDontSatisfyTheAbstractnessInstabilityPrinciple + +warnif count > 0 from a in Application.Assemblies + where a.NormDistFromMainSeq > 0.7 + orderby a.NormDistFromMainSeq descending +select new { + a, + a.NormDistFromMainSeq, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// The **Abstractness versus Instability Diagram** that is shown in the NDepend +// report helps to assess which assemblies are **potentially painful to maintain** +// (i.e concrete and stable) and which assemblies are **potentially useless** +// (i.e abstract and instable). +// +// • **Abstractness**: If an assembly contains many abstract types +// (i.e interfaces and abstract classes) and few concrete types, +// it is considered as abstract. +// +// • **Stability**: An assembly is considered stable if its types +// are used by a lot of types from other assemblies. In this context +// stable means *painful to modify*. +// +// From these metrics, we define the *perpendicular normalized distance of +// an assembly from the idealized line* **A + I = 1** (called *main sequence*). +// This metric is an indicator of the assembly's balance between abstractness +// and stability. We precise that the word *normalized* means that the range +// of values is [0.0 … 1.0]. +// +// This rule warns about assemblies with a *normalized distance* greater than +// than 0.7. +// +// This rules use the default code metric on assembly +// *Normalized Distance from the Main Sequence* explained here: +// https://www.ndepend.com/docs/code-metrics#DitFromMainSeq +// +// These concepts have been originally introduced by *Robert C. Martin* +// in 1994 in this paper: http://www.objectmentor.com/resources/articles/oodmetrc.pdf +// + +// +// Violations of this rule indicate assemblies with an improper +// *abstractness / stability* balance. +// +// • Either the assembly is *potentially painful to maintain* (i.e is massively +// used and contains mostly concrete types). This can be fixed by creating +// abstractions to avoid too high coupling with concrete implementations. +// +// • Either the assembly is *potentially useless* (i.e contains mostly +// abstractions and is not used enough). In such situation, the design +// must be reviewed to see if it can be enhanced. +// +// The severity of issues of this rule is **Low** because +// the *Abstractness/Instability principle* is an information +// about the code structure state but **is not actionable**, +// it doesn't tell precisely what to do obtain a better score. +// +// Fixing actionable issues of others **Architecture** and +// **Code Smells** default rules will necessarily push +// the *Abstractness/Instability principle* scores in the +// right direction. +//]]> + Higher cohesion - lower coupling +// ND1408:HigherCohesionLowerCoupling + +// warnif count > 0 +let abstractNamespaces = JustMyCode.Namespaces.Where( + n => n.ChildTypes.Where(t => !t.IsInterface && !t.IsEnumeration && !t.IsDelegate).Count() == 0 +).ToHashSetEx() + +let concreteNamespaces = JustMyCode.Namespaces.Except(abstractNamespaces).ToHashSetEx() + +from n in concreteNamespaces +let namespacesUsed = n.NamespacesUsed.ExceptThirdParty() +let concreteNamespacesUsed = namespacesUsed.Except(abstractNamespaces) +let abstractNamespacesUsed = namespacesUsed.Except(concreteNamespaces) +orderby concreteNamespacesUsed.Count() descending +select new { + n, + concreteNamespacesUsed , + abstractNamespacesUsed, + // Debt = 50.ToMinutes().ToDebt(), + // Severity = Severity.High +} + +// +// It is deemed as a good software architecture practice to clearly separate +// *abstract* namespaces that contain only abstractions (interfaces, enumerations, delegates) +// from *concrete* namespaces, that contain classes and structures. +// +// Typically, the more concrete namespaces rely on abstract namespaces *only*, +// the more **Decoupled** is the architecture, and the more **Cohesive** are +// classes inside concrete namespaces. +// +// The present code query defines sets of abstract and concrete namespaces +// and show for each concrete namespaces, which concrete and abstract namespaces +// are used. +// + +// +// This query can be transformed into a code rule, depending if you wish to +// constraint your code structure *coupling / cohesion* ratio. +//]]> + Avoid mutually-dependent types +// ND1409:AvoidMutuallyDependentTypes + +warnif count > 0 +from t1 in Application.Types +where t1.NbLinesOfCode > 10 && + (t1.IsClass || t1.IsStructure) + +from t2 in t1.TypesUsingMe +where t2.NbLinesOfCode > 10 && + (t2.IsClass || t2.IsStructure) && + t1.IsUsing(t2) + +// To avoid reporting twice the same pair +// use lexicographically sort of full names of types +// to only take the first one. +where new string[] {t1.FullName, t2.FullName}.OrderBy(x => x).First() == t1.FullName + +// So far we only keep dependencies that involve method call. +// It is possible to comment the two 'where' clauses below to get also +// others kind of dependencies, like usage of typeof(Class). +let t2MethodsUsingT1 = t2.Methods.Where(m => m.IsUsing(t1)).ToArray() +where t2MethodsUsingT1.Length > 0 + +let t1MethodsUsingT2 = t1.Methods.Where(m => m.IsUsing(t2)).ToArray() +where t1MethodsUsingT2.Length > 0 + +select new { + t1, t2, + t2MethodsUsingT1, + t1MethodsUsingT2, + Debt = 20.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule matches pairs of classes or structures that use each others. +// +// In this situation both types are not isolated: each type cannot be +// reviewed, refactored or tested without the other one. +// +// This rule is disabled by default because there are some common +// situations, like when implementing recursive data structures, +// complex serialization or using the visitor pattern, where having +// mutually dependent types is legitimate. +// Just enable this rule if you wish to fix most of its issues. +// + +// +// Fixing mutually-dependent types means identifying the unwanted dependency +// (*t1* using *t2*, or *t2* using *t1*) and then removing this dependency. +// +// Often you'll have to use the **dependency inversion principle** by creating +// one or several interfaces dedicated to abstract one implementation +// from the other one: https://en.wikipedia.org/wiki/Dependency_inversion_principle +//]]> + Example of custom rule to check for dependency +// ND9900:ExplicitId9900 + +warnif count > 0 from a in Assemblies +where +a.IsUsing("Foo1.Foo2".AllowNoMatch().MatchNamespace()) && +(a.Name == @"Foo3") +select new { + a, + a.NbLinesOfCode, + // Debt and Severity / Annual Interest can be modified to estimate well the + // effort to fix the issue and the annual cost to leave the issue unfixed. + Debt = 30.ToMinutes().ToDebt(), + Severity = Severity.High +} +// the assembly Foo3 +// shouldn't use directly +// the namespace Foo3.Foo4 +// because (TODO insert your reason) + +// +// This rule is a **sample rule** that shows how to +// check if a particular dependency exists or not, +// from a code element **A** to a code element **B**, +// **A** and **B** being an *Assembly*, a *Namespace*, a *Type*, +// a *Method* or a *Field*, **A** and **B** being not +// necessarily of same kind (i.e two Assemblies or +// two Namespaces…). +// +// Such rule can be generated: +// +// • by right clicking the cell in the *Dependency Matrix* +// with **B** in row and **A** in column, +// +// • or by right-clicking the concerned arrow in the *Dependency +// Graph* from **A** to **B**, +// +// and in both cases, click the menu +// **Generate a code rule that warns if this dependency exists** +// +// The generated rule will look like this one. +// It is now up to you to adapt this rule to check exactly +// your needs. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + + + API Breaking Changes: Types +// ND1500:APIBreakingChangesTypes + +warnif count > 0 from t in codeBase.OlderVersion().Application.Types +where t.IsPubliclyVisible && + + // The type has been removed, it was not tagged as obsolete + // and its parent assembly hasn't been removed … + ( ( t.WasRemoved() && + !t.ParentAssembly.WasRemoved() && + !t.IsObsolete) || + + // … or the type is not publicly visible anymore + !t.WasRemoved() && !t.NewerVersion().IsPubliclyVisible) + +select new { + t, + NewVisibility = + (t.WasRemoved() ? " " : + t.NewerVersion().Visibility.ToString()), + Debt = 20.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This rule warns if a type publicly visible in the *baseline*, +// is not publicly visible anymore or if it has been removed. +// Clients code using such type will be broken. +// + +// +// Make sure that public types that used to be presented to +// clients, still remain public now, and in the future. +// +// If a public type must really be removed, you can tag it +// with *System.ObsoleteAttribute* with a *workaround message* +// during a few public releases, until it gets removed definitely. +// Notice that this rule doesn't match types removed that were +// tagged as obsolete. +// +// Issues of this rule have a severity equal to **High** +// because an API Breaking change can provoque significant +// friction with consumers of the API. +//]]> + API Breaking Changes: Methods +// ND1501:APIBreakingChangesMethods + +warnif count > 0 from m in codeBase.OlderVersion().Application.Methods +where m.IsPubliclyVisible && + + // The method has been removed, it was not tagged as obsolete + // and its parent type hasn't been removed … + ( ( m.WasRemoved() && + !m.ParentType.WasRemoved() && + !m.IsObsolete ) + + // … or the method is not publicly visible anymore + // but the parent type is still publicly visible + || (!m.WasRemoved() && !m.NewerVersion().IsPubliclyVisible && + m.ParentType.NewerVersion().IsPubliclyVisible) + + // … or the method return type has changed + || (!m.WasRemoved() && m.ReturnType != null && m.NewerVersion().ReturnType != null + && m.ReturnType.FullName != m.NewerVersion().ReturnType.FullName) + ) + +//-------------------------------------- +// Handle special case: if between two versions a regular property becomes +// an auto-property (or vice-versa) the property getter/setter method have +// a different value for IMethod.IsGeneratedByCompiler +// since auto-property getter/setter are marked as generated by the compiler. +// +// If a method IsGeneratedByCompiler value changes between two versions, +// NDepend doesn't pair the newer/older occurences of the method. +// +// Hence in such situation, a public method is seen as added +// and a public method is seen as removed, but the API is not broken! +// The equivalentMethod-check below avoids reporting such +// API Breaking Change false-positive. +let equivalentMethod = m.WasRemoved() && m.ParentType.IsPresentInBothBuilds() ? + m.ParentType.NewerVersion().Methods + .FirstOrDefault(m1 => + m1.IsPubliclyVisible && + m1.Name == m.Name && + m1.IsGeneratedByCompiler != m.IsGeneratedByCompiler && + (m1.ReturnType == null || m.ReturnType == null || m1.ReturnType.FullName == m.ReturnType.FullName) + ) + : null +where equivalentMethod == null +//-------------------------------------- + + +select new { + m, + NewVisibility = + (m.WasRemoved() ? " " : + m.NewerVersion().Visibility.ToString()), + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This rule warns if a method publicly visible in the *baseline*, +// is not publicly visible anymore or if it has been removed. +// Clients code using such method will be broken. +// + +// +// Make sure that public methods that used to be presented to +// clients, still remain public now, and in the future. +// +// If a public method must really be removed, you can tag it +// with *System.ObsoleteAttribute* with a *workaround message* +// during a few public releases, until it gets removed definitely. +// Notice that this rule doesn't match methods removed that were +// tagged as obsolete. +// +// Issues of this rule have a severity equal to **High** +// because an API Breaking change can provoque significant +// friction with consumers of the API. +// +]]> + API Breaking Changes: Fields +// ND1502:APIBreakingChangesFields + +warnif count > 0 from f in codeBase.OlderVersion().Application.Fields +where f.IsPubliclyVisible && + + // The field has been removed, it was not tagged as obsolete + // and its parent type hasn't been removed … + ( ( f.WasRemoved() && + !f.ParentType.WasRemoved() && + !f.IsObsolete) + + // … or the field is not publicly visible anymore + // but the parent type is still publicly visible + || (!f.WasRemoved() && !f.NewerVersion().IsPubliclyVisible && + f.ParentType.NewerVersion().IsPubliclyVisible) + + // … or the field type has changed + || (!f.WasRemoved() && f.FieldType != null && f.NewerVersion().FieldType != null + && f.FieldType.FullName != f.NewerVersion().FieldType.FullName) + ) +select new { + f, + NewVisibility = + (f.WasRemoved() ? " " : + f.NewerVersion().Visibility.ToString()), + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This rule warns if a field publicly visible in the *baseline*, +// is not publicly visible anymore or if it has been removed. +// Clients code using such field will be broken. +// + +// +// Make sure that public fields that used to be presented to +// clients, still remain public now, and in the future. +// +// If a public field must really be removed, you can tag it +// with *System.ObsoleteAttribute* with a *workaround message* +// during a few public releases, until it gets removed definitely. +// Notice that this rule doesn't match fields removed that were +// tagged as obsolete. +// +// Issues of this rule have a severity equal to **High** +// because an API Breaking change can provoque significant +// friction with consumers of the API. +//]]> + API Breaking Changes: Interfaces and Abstract Classes +// ND1503:APIBreakingChangesInterfacesAndAbstractClasses + +warnif count > 0 from tNewer in Application.Types where + (tNewer.IsInterface || tNewer.IsClass && tNewer.IsAbstract) && + tNewer.IsPubliclyVisible && + tNewer.IsPresentInBothBuilds() + +let tOlder = tNewer.OlderVersion() where tOlder.IsPubliclyVisible + +let methodsRemoved = tOlder.Methods.Where(m => m.IsAbstract && m.WasRemoved()) +let methodsAdded = tNewer.Methods.Where(m => m.IsAbstract && m.WasAdded()) + +where methodsAdded.Count() > 0 || methodsRemoved.Count() > 0 +select new { + tNewer, + methodsAdded, + methodsRemoved, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This rule warns if a publicly visible interface or abstract class +// has been changed and contains new abstract methods or +// if some abstract methods have been removed. +// +// Clients code that implement such interface or derive from +// such abstract class will be broken. +// + +// +// Make sure that the public contracts of interfaces and abstract classes +// that used to be presented to clients, remain stable now, and in the future. +// +// If a public contract must really be changed, you can tag +// abstract methods that will be removed with *System.ObsoleteAttribute* +// with a *workaround message* during a few public releases, until it gets +// removed definitely. +// +// Issues of this rule have a severity equal to **High** +// because an API Breaking change can provoque significant +// friction with consummers of the API. +// The severity is not set to **Critical** because an interface +// is not necessarily meant to be implemented by the consummer +// of the API. +//]]> + Broken serializable types +// ND1504:BrokenSerializableTypes + +warnif count > 0 + +from t in Application.Types where + + // Collect types tagged with SerializableAttribute + t.HasAttribute("System.SerializableAttribute".AllowNoMatch()) && + !t.IsDelegate && + t.IsPresentInBothBuilds() && + t.HasAttribute(t) + + // Find newer and older versions of NonSerializedAttribute + let newNonSerializedAttribute = ThirdParty.Types.WithFullName("System.NonSerializedAttribute").SingleOrDefault() + let oldNonSerializedAttribute = newNonSerializedAttribute == null ? null : newNonSerializedAttribute.OlderVersion() + + // Find added or removed fields not marked with NonSerializedAttribute + let addedInstanceField = from f in t.InstanceFields where + f.WasAdded() && + (newNonSerializedAttribute == null || !f.HasAttribute(newNonSerializedAttribute)) + select f + let removedInstanceField = from f in t.OlderVersion().InstanceFields where + f.WasRemoved() && + (oldNonSerializedAttribute == null || !f.HasAttribute(oldNonSerializedAttribute)) + select f + where addedInstanceField.Count() > 0 || removedInstanceField.Count() > 0 + +select new { + t, + addedInstanceField, + removedInstanceField, + Debt = 20.ToMinutes().ToDebt(), + Severity = Severity.Critical +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This rule warns about breaking changes in types tagged with +// *SerializableAttribute*. +// +// To do so, this rule searches for serializable type with serializable +// instance fields added or removed. Notice that it doesn't take account +// of fields tagged with *NonSerializedAttribute*. +// +// From http://msdn.microsoft.com/library/system.serializableattribute.aspx : +// "All the public and private fields in a type that are marked by the +// *SerializableAttribute* are serialized by default, unless the type +// implements the *ISerializable* interface to override the serialization process. +// The default serialization process excludes fields that are marked +// with the *NonSerializedAttribute* attribute." +// + +// +// Make sure that the serialization process of serializable types remains +// stable now, and in the future. +// +// Else you'll have to deal with **Version Tolerant Serialization** +// that is explained here: +// https://msdn.microsoft.com/en-us/library/ms229752(v=vs.110).aspx +// +// Issues of this rule have a severity equal to **High** +// because an API Breaking change can provoque significant +// friction with consummers of the API. +//]]> + Avoid changing enumerations Flags status +// ND1505:AvoidChangingEnumerationsFlagsStatus + +warnif count > 0 + +let oldFlags = codeBase.OlderVersion().ThirdParty.Types.WithFullName("System.FlagsAttribute").FirstOrDefault() +let newFlags = ThirdParty.Types.WithFullName("System.FlagsAttribute").FirstOrDefault() +where oldFlags != null && newFlags != null + +from t in Application.Types where + t.IsEnumeration && + t.IsPresentInBothBuilds() +let hasFlagsAttributeNow = t.HasAttribute(newFlags) +let usedToHaveFlagsAttribute = t.OlderVersion().HasAttribute(oldFlags) +where hasFlagsAttributeNow != usedToHaveFlagsAttribute +select new { + t, + hasFlagsAttributeNow, + usedToHaveFlagsAttribute, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This rule matches enumeration types that used to be tagged +// with *FlagsAttribute* in the *baseline*, and not anymore. +// It also matches the opposite, enumeration types that are now +// tagged with *FlagsAttribute*, and were not tagged in the *baseline*. +// +// Being tagged with *FlagsAttribute* is a strong property for an enumeration. +// Not so much in terms of *behavior* (only the *enum.ToString()* method +// behavior changes when an enumeration is tagged with *FlagsAttribute*) +// but in terms of *meaning*: is the enumeration a **range of values** +// or a **range of flags**? +// +// As a consequence, changing the *FlagsAttribute*s status of an enumeration can +// have significant impact for its clients. +// + +// +// Make sure the *FlagsAttribute* status of each enumeration remains stable +// now, and in the future. +//]]> + API: New publicly visible types +from t in Application.Types +where t.IsPubliclyVisible && + + // The type has been removed and its parent assembly hasn't been removed … + ( (t.WasAdded() && !t.ParentAssembly.WasAdded()) || + + // … or the type existed but was not publicly visible + !t.WasAdded() && !t.OlderVersion().IsPubliclyVisible) + +select new { + t, + OldVisibility = + (t.WasAdded() ? " " : + t.OlderVersion().Visibility.ToString()), +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists types that are new in the public +// surface of the analyzed assemblies. +//]]> + API: New publicly visible methods +from m in Application.Methods +where m.IsPubliclyVisible && + + // The method has been removed and its parent assembly hasn't been removed … + ( (m.WasAdded() && !m.ParentType.WasAdded()) || + + // … or the method existed but was not publicly visible + !m.WasAdded() && !m.OlderVersion().IsPubliclyVisible) + +//-------------------------------------- +// Handle special case: if between two versions a regular property becomes +// an auto-property (or vice-versa) the property getter/setter method have +// a different value for IMethod.IsGeneratedByCompiler +// since auto-property getter/setter are marked as generated by the compiler. +// +// If a method IsGeneratedByCompiler value changes between two versions, +// NDepend doesn't pair the newer/older occurences of the method. +// +// Hence in such situation, a public method is seen as added +// and a public method is seen as removed, but the API is not broken! +// The equivalentMethod-check below avoids reporting such +// API Breaking Change false-positive. +let equivalentMethod = m.WasAdded() && m.ParentType.IsPresentInBothBuilds() ? + m.ParentType.OlderVersion().Methods + .FirstOrDefault(m1 => + m1.IsPubliclyVisible && + m1.Name == m.Name && + m1.IsGeneratedByCompiler != m.IsGeneratedByCompiler) + : null +where equivalentMethod == null +//-------------------------------------- + +select new { + m, + OldVisibility = + (m.WasAdded() ? " " : + m.OlderVersion().Visibility.ToString()) +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists methods that are new in the public +// surface of the analyzed assemblies. +// +]]> + API: New publicly visible fields +from f in Application.Fields +where f.IsPubliclyVisible && + + // The method has been removed and its parent assembly hasn'f been removed … + ( (f.WasAdded() && !f.ParentType.WasAdded()) || + + // … or the t existed but was not publicly visible + !f.WasAdded() && !f.OlderVersion().IsPubliclyVisible) + +select new { + f, + OldVisibility = + (f.WasAdded() ? " " : + f.OlderVersion().Visibility.ToString()) +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists fields that are new in the public +// surface of the analyzed assemblies. +//]]> + + + Code should be tested +// ND1600:CodeShouldBeTested + +warnif count > 0 + +// This lambda infers a factor in the range [0,1] from a sequence of distinct types used, +// based on how many of these types are actually concrete (i.e are not interfaces or enumeration). +// Primitive types (int, bool...) are eliminated from the sequence of types used +// by filtering types declared in the namespace System. +let abstractionUsageFactorFormula = new Func,double>(typesUsed => + typesUsed.Count(t => t.ParentNamespace.Name != "System" && !t.IsInterface && !t.IsEnumeration) + / (1 + typesUsed.Count(t => t.ParentNamespace.Name != "System"))) + + +from method in Application.Methods + where !method.IsExcludedFromCoverage && method.NbLinesOfCode >= 0 && method.PercentageCoverage < 100 + + // Factor in case method is partially covered + let uncoverageFactor = ((100 - method.PercentageCoverage) / 100).Value + + // Complexity factor + let complexityFactor = ((method.CyclomaticComplexity ?? method.ILCyclomaticComplexity) + method.ILNestingDepth).Linear(0, 0.1, 10, 1).Value + + // Not my code is often generated code and is in general easier to get tested since test can be generated as well. + let justMyCodeFactor = (JustMyCode.Contains(method) ? 1 : 0.4) + + // abstractionUsageFactor reflects the fact that code that relies on interfaces + // is easier to test that code that relies on concrete classes. + let abstractionUsageFactor = 0.7 + 0.3 *abstractionUsageFactorFormula(method.MembersUsed.Select(m => m.ParentType).Distinct()) + + // The usageFactor depends on the method 'rank' that is a value + // indicating if the method is often used or not + let usageFactor = (method.Rank / (method.Rank + 4)).Value + + // It is more complicated to write tests for non publicly visible methods + let visibilityFactor = method.Visibility.EqualsAny(Visibility.Public, Visibility.Internal) ? 1 : + method.Visibility != Visibility.Private ? 1.1 : 1.2 + + // Is is more complicated to write tests for methods that read mutable static fields + // whose changing state is shared across tests executions. + let staticFieldUsageFactor = method.ReadsMutableTypeState ? 1.3 : 1.0 + + + // Both "effort to write tests" and "annual cost to not test" for a method + // is determined by several factors in the range [0,1] that multiplies the effortToDevelop + let effortToDevelopInMinutes = method.EffortToDevelop().Value.TotalMinutes + + let effortToWriteTests = Math.Max(2, // Minimum 2 minutes per method not tested + effortToDevelopInMinutes * + uncoverageFactor * + complexityFactor * + justMyCodeFactor * + abstractionUsageFactor * + visibilityFactor * + staticFieldUsageFactor).ToMinutes().ToDebt() + + let annualCostToNotFix = Math.Max(2, // Minimum 2 minutes per method not tested + effortToDevelopInMinutes * + usageFactor * + uncoverageFactor * + justMyCodeFactor).ToMinutes().ToAnnualInterest() + + orderby annualCostToNotFix.Value descending + +select new { + method, + method.PercentageCoverage, + method.NbLinesOfCode, + method.NbLinesOfCodeNotCovered, + method.CyclomaticComplexity, + Debt = effortToWriteTests, + AnnualInterest = annualCostToNotFix, + + // BreakingPoint = effortToWriteTests.BreakingPoint(annualCostToNotFix), + + // Uncomment the line below to tinker with various factors + // uncoverageFactor, complexityFactor , justMyCodeFactor , abstractionUsageFactor, visibilityFactor, staticFieldUsageFactor +} + + +// +// This rule lists methods not covered at all by test +// or partially covered by tests. +// +// For each match, the rules estimates the **technical debt**, i.e +// the effort to write unit and integration tests for the method. +// The estimation is based on the effort to develop the code element +// multiplied by factors in the range ]0,1.3] based on +// +// • the method code size and complexity +// +// • the actual percentage coverage +// +// • the abstractness of types used, because relying on classes instead of +// interfaces makes the code more difficult to test +// +// • the method visibility because testing private or protected +// methods is more difficult than testing public and internal ones +// +// • the fields used by the method, because is is more complicated to +// write tests for methods that read mutable static fields whose changing +// state is shared across tests executions. +// +// • whether the method is considered *JustMyCode* or not because *NotMyCode* +// is often generated easier to get tested since tests can be generated as well. +// +// This rule is necessarily a large source of technical debt, since +// the code left untested is by definition part of the technical debt. +// +// This rule also estimates the **annual interest**, i.e the annual cost +// to let the code uncovered, based on the effort to develop the +// code element, multiplied by factors based on usage of the code element. +// + +// +// Write unit tests to test and cover the methods and their parent classes +// matched by this rule. +//]]> + New Methods should be tested +// ND1601:NewMethodsShouldBeTested + +warnif count > 0 +from m in Application.Methods where + m.NbLinesOfCode > 0 && + m.PercentageCoverage < 30 && + m.WasAdded() + orderby m.NbLinesOfCode descending, + m.NbLinesOfCodeNotCovered , + m.PercentageCoverage +select new { + m, + m.PercentageCoverage, + m.NbLinesOfCode, + m.NbLinesOfCodeNotCovered, + + // Simplistic Debt estimation, because the effort to write tests for a method not 100% tested + // is already estimated properly with the rule "Code should be tested". + Debt = m.NbLinesOfCodeNotCovered.Linear(1,2, 10,10).ToMinutes().ToDebt(), + + Severity = Severity.High +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// This rule operates only on methods added or refactored since the baseline. +// +// This rule is executed only if some code coverage data is imported +// from some code coverage files. +// +// It is important to write code mostly covered by tests +// to achieve *maintainable* and *non-error-prone* code. +// +// In real-world, many code bases are poorly covered by tests. +// However it is not practicable to stop the development for months +// to refactor and write tests to achieve high code coverage ratio. +// +// Hence it is recommended that each time a method (or a type) gets added, +// the developer takes the time to write associated unit-tests to cover it. +// +// Doing so will help to increase significantly the maintainability of the code base. +// You'll notice that quickly, refactoring will also be driven by testability, +// and as a consequence, the overall code structure and design will increase as well. +// +// Issues of this rule have a **High** severity because they reflect +// an actual trend to not care about writing tests on refactored code. +// + +// +// Write unit-tests to cover the code of most methods and classes added. +//]]> + Methods refactored should be tested +// ND1602:MethodsRefactoredShouldBeTested + +warnif count > 0 +from m in Application.Methods where + m.PercentageCoverage < 30 && + m.CodeWasChanged() + orderby m.NbLinesOfCode descending, + m.NbLinesOfCodeNotCovered , + m.PercentageCoverage +select new { + m, + m.PercentageCoverage, + m.NbLinesOfCode, + m.NbLinesOfCodeNotCovered, + + // Simplistic Debt estimation, because the effort to write tests for a method not 100% tested + // is already estimated properly with the rule "Code should be tested". + Debt = m.NbLinesOfCodeNotCovered.Linear(1,2, 10,10).ToMinutes().ToDebt(), + + Severity = Severity.High +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// This rule operates only on methods added or refactored since the baseline. +// +// This rule is executed only if some code coverage data is imported +// from some code coverage files. +// +// It is important to write code mostly covered by tests +// to achieve *maintainable* and *non-error-prone* code. +// +// In real-world, many code bases are poorly covered by tests. +// However it is not practicable to stop the development for months +// to refactor and write tests to achieve high code coverage ratio. +// +// Hence it is recommended that each time a method (or a type) gets refactored, +// the developer takes the time to write associated unit-tests to cover it. +// +// Doing so will help to increase significantly the maintainability of the code base. +// You'll notice that quickly, refactoring will also be driven by testability, +// and as a consequence, the overall code structure and design will increase as well. +// +// Issues of this rule have a **High** severity because they reflect +// an actual trend to not care about writing tests on refactored code. +// + +// +// Write unit-tests to cover the code of most methods and classes refactored. +//]]> + Assemblies Namespaces and Types should be tested +// ND1603:AssembliesNamespacesAndTypesShouldBeTested + +warnif count > 0 +from elem in CodeElementParents +where elem.NbLinesOfCode > 0 && + elem.PercentageCoverage == 0 && + elem.Parent.PercentageCoverage != 0 +orderby elem.NbLinesOfCode descending +select new { + elem, + elem.NbLinesOfCodeNotCovered, + Debt = 4.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// This rule lists assemblies, namespaces and types that are not +// covered at all by unit tests. +// +// If a parent is matched its children are not matched. For example +// if a namespace is matched, its child types are not matched. +// +// This rule goal is not to collide with the **Code should be tested** +// rule that lists uncovered code for each method and infer the effort +// to write unit tests (the *Debt*) and the annual cost to let the code +// untested (the Annual Interest). +// +// This rule goal is to inform of large code elements left untested. +// As a consequence the *Debt* per issue is only 4 minutes and the +// severity of the issues is *Low*. +// +// +// +// Write unit and integration tests to cover, even partially, +// code elements matched by this rule. +// +// Then use issues of the rules **Code should be tested**, +// **New Methods should be tested** and +// **Methods refactored should be tested** +// to write more tests where it matters most, and eventually +// refactor some code to make it more testable. +//]]> + Types almost 100% tested should be 100% tested +// ND1604:TypesAlmost100PercentTestedShouldBe100PercentTested + +warnif count > 0 +from t in Application.Types where + t.PercentageCoverage >= 95 && + t.PercentageCoverage <= 99 && + !t.IsGeneratedByCompiler + + let methodsCulprit = t.Methods.Where(m => m.PercentageCoverage < 100) + + orderby t.NbLinesOfCode descending , + t.NbLinesOfCodeNotCovered , + t.PercentageCoverage +select new { + t, + t.PercentageCoverage, + t.NbLinesOfCode, + t.NbLinesOfCodeNotCovered, + methodsCulprit, + + // Simplistic Debt estimation, because the effort to write tests for a type not 100% tested + // is already estimated properly with the rule "Code should be tested". + Debt = t.NbLinesOfCodeNotCovered.Linear(1,2, 20,20).ToMinutes().ToDebt(), + + Severity = Severity.High +} + +// +// This rule is executed only if some code coverage data is imported +// from some code coverage files. +// +// Often covering the few percents of remaining uncovered code of a class, +// requires as much work as covering the first 90%. +// For this reason, often teams estimate that 90% coverage is enough. +// However *untestable code* usually means *poorly written code* +// which usually leads to *error prone code*. +// So it might be worth refactoring and making sure to cover the few uncovered lines of code +// **because most tricky bugs might come from this small portion of hard-to-test code**. +// +// Not all classes should be 100% covered by tests (like UI code can be hard to test) +// but you should make sure that most of the logic of your application +// is defined in some *easy-to-test classes*, 100% covered by tests. +// +// Issues of this rule have a **High** severity because as explained, +// such situation is *bug-prone*. +// + +// +// Write more unit-tests dedicated to cover code not covered yet. +// If you find some *hard-to-test code*, it is certainly a sign that this code +// is not *well designed* and hence, needs refactoring. +//]]> + Namespaces almost 100% tested should be 100% tested +// ND1605:NamespacesAlmost100PercentTestedShouldBe100PercentTested + +warnif count > 0 +from n in Application.Namespaces where + n.PercentageCoverage >= 95 && + n.PercentageCoverage <= 99 + + let methodsCulprit = n.ChildMethods.Where(m => m.PercentageCoverage < 100) + + orderby n.NbLinesOfCode descending , + n.NbLinesOfCodeNotCovered , + n.PercentageCoverage +select new { + n, + n.PercentageCoverage, + n.NbLinesOfCode, + n.NbLinesOfCodeNotCovered, + methodsCulprit, + + // Simplistic Debt estimation, because the effort to write tests for a type not 100% tested + // is already estimated properly with the rule "Code should be tested". + Debt = n.NbLinesOfCodeNotCovered.Linear(1,2, 50,60).ToMinutes().ToDebt(), + + Severity = Severity.High +} + +// +// This rule is executed only if some code coverage data is imported +// from some code coverage files. +// +// Often covering the few percents of remaining uncovered code of +// one or several classes in a namespace +// requires as much work as covering the first 90%. +// For this reason, often teams estimate that 90% coverage is enough. +// However *untestable code* usually means *poorly written code* +// which usually leads to *error prone code*. +// So it might be worth refactoring and making sure to cover the few uncovered lines of code +// **because most tricky bugs might come from this small portion of hard-to-test code**. +// +// Not all classes should be 100% covered by tests (like UI code can be hard to test) +// but you should make sure that most of the logic of your application +// is defined in some *easy-to-test classes*, 100% covered by tests. +// +// Issues of this rule have a **High** severity because as explained, +// such situation is *bug-prone*. +// + +// +// Write more unit-tests dedicated to cover code not covered yet in the namespace. +// If you find some *hard-to-test code*, it is certainly a sign that this code +// is not *well designed* and hence, needs refactoring. +//]]> + Types that used to be 100% covered by tests should still be 100% covered +// ND1606:TypesThatUsedToBe100PercentCoveredByTestsShouldStillBe100PercentCovered + +warnif count > 0 +from t in JustMyCode.Types where + t.IsPresentInBothBuilds() && + t.OlderVersion().PercentageCoverage == 100 && + t.PercentageCoverage < 100 + +from m in t .MethodsAndConstructors where + m.NbLinesOfCode> 0 && + m.PercentageCoverage < 100 && + !m.IsExcludedFromCoverage + +select new { + m, + m.PercentageCoverage, + + // Simplistic Debt estimation, because the effort to write tests for a method not 100% tested + // is already estimated properly with the rule "Code should be tested". + Debt = t.NbLinesOfCodeNotCovered.Linear(1,2, 10,10).ToMinutes().ToDebt(), + + Severity = Severity.High +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This rule is executed only if some code coverage data is imported +// from some code coverage files. +// +// Often covering 10% of remaining uncovered code of a class, +// requires as much work as covering the first 90%. +// For this reason, typically teams estimate that 90% coverage is enough. +// However *untestable code* usually means *poorly written code* +// which usually leads to *error prone code*. +// So it might be worth refactoring and making sure to cover the 10% remaining code +// **because most tricky bugs might come from this small portion of hard-to-test code**. +// +// Not all classes should be 100% covered by tests (like UI code can be hard to test) +// but you should make sure that most of the logic of your application +// is defined in some *easy-to-test classes*, 100% covered by tests. +// +// In this context, this rule warns when a type fully covered by tests is now only partially covered. +// +// Issues of this rule have a **High** severity because often, +// a type that used to be 100% and is not covered anymore +// is a bug-prone situation that should be carefully handled. +// + +// +// Write more unit-tests dedicated to cover code not covered anymore. +// If you find some *hard-to-test code*, it is certainly a sign that this code +// is not *well designed* and hence, needs refactoring. +// +// You'll find code impossible to cover by unit-tests, like calls to *MessageBox.Show()*. +// An infrastructure must be defined to be able to *mock* such code at test-time. +//]]> + Types tagged with FullCoveredAttribute should be 100% covered +// ND1607:TypesTaggedWithFullCoveredAttributeShouldBe100PercentCovered + +warnif count > 0 +from t in Application.Types where + t.HasAttribute ("NDepend.Attributes.FullCoveredAttribute".AllowNoMatch()) && + t.PercentageCoverage < 100 + +from m in t .MethodsAndConstructors where + m.NbLinesOfCode> 0 && + m.PercentageCoverage < 100 && + !m.IsExcludedFromCoverage + +select new { + m, + m.PercentageCoverage, + m.NbLinesOfCodeNotCovered, + m.NbLinesOfCode, + + // Simplistic Debt estimation, because the effort to write tests for a method not 100% tested + // is already estimated properly with the rule "Code should be tested". + Debt = m.NbLinesOfCodeNotCovered.Linear(1,2, 10,10).ToMinutes().ToDebt(), + + Severity = Severity.High +} + +// +// This rule lists methods partially covered by tests, of types tagged with +// **FullCoveredAttribute**. +// +// This rule is executed only if some code coverage data is imported +// from some code coverage files. +// +// By using a **FullCoveredAttribute**, you can express in source code the intention +// that a class is 100% covered by tests, and should remain 100% covered in the future. +// If you don't want to link *NDepend.API.dll*, +// you can use your own attribute and adapt the source code of this rule. +// +// Benefits of using a **FullCoveredAttribute** are twofold: +// Not only the intention is expressed in source code, +// but it is also continuously checked by the present rule. +// +// Often covering 10% of remaining uncovered code of a class, +// requires as much work as covering the first 90%. +// For this reason, often teams estimate that 90% coverage is enough. +// However *untestable code* usually means *poorly written code* which usually means *error prone code*. +// So it might be worth refactoring and making sure to cover the 10% remaining code +// **because most tricky bugs might come from this small portion of hard-to-test code**. +// +// Not all classes should be 100% covered by tests (like UI code can be hard to test) +// but you should make sure that most of the logic of your application +// is defined in some *easy-to-test classes*, 100% covered by tests. +// +// Issues of this rule have a **High** severity because often, +// a type that used to be 100% and is not covered anymore +// is a bug-prone situation that should be carefully handled. +// + +// +// Write more unit-tests dedicated to cover code of matched classes not covered yet. +// If you find some *hard-to-test code*, it is certainly a sign that this code +// is not *well designed* and hence, needs refactoring. +//]]> + Types 100% covered should be tagged with FullCoveredAttribute +// ND1608:Types100PercentCoveredShouldBeTaggedWithFullCoveredAttribute + +warnif count > 0 from t in JustMyCode.Types where + !t.HasAttribute ("NDepend.Attributes.FullCoveredAttribute".AllowNoMatch()) && + t.PercentageCoverage == 100 && + !t.IsGeneratedByCompiler +select new { + t, + t.NbLinesOfCode, + Debt = 3.ToMinutes().ToDebt(), // It is fast to add such attribute to a type. + Severity = Severity.Low +} + +// +// This rule is executed only if some code coverage data is imported +// from some code coverage files. +// +// By using a **FullCoveredAttribute**, you can express in source code the intention +// that a class is 100% covered by tests, and should remain 100% covered in the future. +// +// Benefits of using a **FullCoveredAttribute** are twofold: +// Not only the intention is expressed in source code, +// but it is also continuously checked by the present rule. +// +// Issues of this rule have an **Low** severity because they don't reflect +// a problem, but provide an advice for potential improvement. +// + +// +// Just tag types 100% covered by tests with the **FullCoveredAttribute** +// that can be found in *NDepend.API.dll*, +// or by an attribute of yours defined in your own code +// (in which case this rule must be adapted). +//]]> + Methods should have a low C.R.A.P score +// ND1609:MethodsShouldHaveALowCRAPScore + +warnif count > 0 +from m in JustMyCode.Methods + +// Don't match too short methods +where m.NbLinesOfCode > 10 && m.CoverageDataAvailable + +let CC = m.CyclomaticComplexity +let uncov = (100 - m.PercentageCoverage) / 100f +let CRAP = (double)(CC * CC * uncov * uncov * uncov) + CC +where CRAP != null && CRAP > 30 +orderby CRAP descending, m.NbLinesOfCode descending +select new { + m, + CRAP, + CC, + m.PercentageCoverage, m.NbLinesOfCode, + + // CRAP score equals 30 => 10 minutes debt + // CRAP score equals 3000 => 3 hours to write tests + Debt = CRAP.Linear(30,10, 3000, 3*60).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is executed only if some code coverage data is imported +// from some code coverage files. +// +// So far this rule is disabled because other code coverage rules +// assess properly code coverage issues. +// +// **Change Risk Analyzer and Predictor** (i.e. CRAP) is a code metric +// that helps in pinpointing overly both complex and untested code. +// Is has been first defined here: +// http://www.artima.com/weblogs/viewpost.jsp?thread=215899 +// +// The Formula is: **CRAP(m) = CC(m)^2 * (1 – cov(m)/100)^3 + CC(m)** +// +// • where *CC(m)* is the *cyclomatic complexity* of the method *m* +// +// • and *cov(m)* is the *percentage coverage* by tests of the method *m* +// +// Matched methods cumulates two highly *error prone* code smells: +// +// • A complex method, difficult to develop and maintain. +// +// • Non 100% covered code, difficult to refactor without introducing any regression bug. +// +// The higher the CRAP score, the more painful to maintain and error prone is the method. +// +// An arbitrary threshold of 30 is fixed for this code rule as suggested by inventors. +// +// Notice that no amount of testing will keep methods with a Cyclomatic Complexity +// higher than 30, out of CRAP territory. +// +// Notice that this rule doesn't match too short method +// with less than 10 lines of code. +// + +// +// In such situation, it is recommended to both refactor the complex method logic +// into several smaller and less complex methods +// (that might belong to some new types especially created), +// and also write unit-tests to full cover the refactored logic. +// +// You'll find code impossible to cover by unit-tests, like calls to *MessageBox.Show()*. +// An infrastructure must be defined to be able to *mock* such code at test-time. +//]]> + Test Methods + +let testAttr = ThirdParty.Types.WithNameIn("FactAttribute", "TestAttribute", "TestCaseAttribute") +let testMethods = Methods.TaggedWithAnyAttributes(testAttr) +from m in testMethods +select m + +// +// We advise to not include test assemblies in code analyzed by NDepend. +// We estimate that it is acceptable and practical to lower the quality gate of test code, +// because the important measures for tests are: +// +// • The coverage ratio, +// +// • And the amount of logic results asserted: This includes both +// assertions in test code, and assertions in code covered by tests, +// like *Code Contract* assertions and *Debug.Assert(…)* assertions. +// +// But if you wish to enforce the quality of test code, you'll need to +// consider test assemblies in your list of application assemblies +// analyzed by NDepend. +// +// In such situation, this code query lists tests methods and you can +// reuse this code in custom rules. +//]]> + Methods directly called by test Methods + +let testAttr = ThirdParty.Types.WithNameIn("FactAttribute", "TestAttribute", "TestCaseAttribute") +let testMethods = Methods.TaggedWithAnyAttributes(testAttr).ToHashSetEx() + +// --- Uncomment this line if your test methods are in dedicated test assemblies --- +//let testAssemblies = testMethods.ParentAssemblies().ToHashSetEx() + +from m in Application.Methods.UsedByAny(testMethods) + +// --- Uncomment this line if your test methods are in dedicated test assemblies --- +//where !testAssemblies.Contains(m.ParentAssembly) + +select new { m , + calledByTests = m.MethodsCallingMe.Intersect(testMethods ), + // --- Uncomment this line if your project import some coverage data --- + // m.PercentageCoverage +} + + +// +// This query lists all methods directly called by tests methods. +// Overrides of virtual and abstract methods, called through polymorphism, are not listed. +// Methods solely invoked through a delegate are not listed. +// Methods solely invoked through reflection are not listed. +// +// We advise to not include test assemblies in code analyzed by NDepend. +// We estimate that it is acceptable and practical to lower the quality gate of test code, +// because the important measures for tests are: +// +// • The coverage ratio, +// +// • And the amount of logic results asserted: This includes both +// assertions in test code, and assertions in code covered by tests, +// like *Code Contract* assertions and *Debug.Assert(…)* assertions. +// +// But if you wish to run this code query, +// you'll need to consider test assemblies in your list of +// application assemblies analyzed by NDepend. +//]]> + Methods directly and indirectly called by test Methods + +let testAttr = from t in ThirdParty.Types.WithNameIn("FactAttribute", "TestAttribute", "TestCaseAttribute") select t +let testMethods = Methods.TaggedWithAnyAttributes(testAttr) + +// --- Uncomment this line if your test methods are in dedicated test assemblies --- +// let testAssemblies = testMethods.ParentAssemblies().ToHashSetEx() + +let depthOfCalledByTest = Application.Methods.DepthOfIsUsedByAny(testMethods) +from pair in depthOfCalledByTest +where pair.Value > 0 +orderby pair.Value ascending +// --- Uncomment this line if your test methods are in dedicated test assemblies --- +//&& !testAssemblies.Contains(pair.CodeElement.ParentAssembly) + +select new { + method = pair.CodeElement, + // (depthOfCalledByTests == 1) means that the method is directly called by tests + // (depthOfCalledByTests == 2) means that the method is directly called by a method directly called by tests + // … + depthOfCalledByTests = pair.Value, + nbLinesOfCode = pair.CodeElement.NbLinesOfCode, + // --- Uncomment this line if your project import some coverage data --- + // m.PercentageCoverage +} + +// +// This query lists all methods *directly or indirectly* called by tests methods. +// *Indirectly* called by a test means that a test method calls a method, that calls a method… +// From this recursion, a code metric named *depthOfCalledByTests* is inferred, +// The value *1* means directly called by test, +// the value *2* means called by a method that is called by a test… +// +// Overrides of virtual and abstract methods, called through polymorphism, are not listed. +// Methods solely invoked through a delegate are not listed. +// Methods solely invoked through reflection are not listed. +// +// We advise to not include test assemblies in code analyzed by NDepend. +// We estimate that it is acceptable and practical to lower the quality gate of test code, +// because the important measures for tests are: +// +// • The coverage ratio, +// +// • And the amount of logic results asserted: This includes both +// assertions in test code, and assertions in code covered by tests, +// like *Code Contract* assertions and *Debug.Assert(…)* assertions. +// +// But if you wish to run this code query, +// you'll need to consider test assemblies in your list of +// application assemblies analyzed by NDepend. +//]]> + + + Potentially Dead Types +// ND1700:PotentiallyDeadTypes + +warnif count > 0 +// Filter procedure for types that should'nt be considered as dead +let canTypeBeConsideredAsDeadProc = new Func( + t => !t.IsPublic && // Public types might be used by client applications of your assemblies. + t.Name != "Program" && + !t.IsGeneratedByCompiler && + + // If you don't want to link NDepend.API.dll, you can use your own + // IsNotDeadCodeAttribute or UsedImplicitlyAttribute + // and adapt the source code of this rule. + !t.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) && + !t.HasAttribute("JetBrains.Annotations.UsedImplicitlyAttribute".AllowNoMatch()) && + + // Exclude static types that define only const fields + // because they cannot be seen as used in IL code. + !(t.IsStatic && t.NbMethods == 0 && !t.Fields.Where(f => !f.IsLiteral).Any()) && + + // Entity Framework ModelSnapshot classes are used only by the EF infrastructure. + !t.DeriveFrom("Microsoft.EntityFrameworkCore.Infrastructure.ModelSnapshot".AllowNoMatch()) && + + // ASP.NET MVC Controller classes are used only by the infrastructure. + !t.SimpleName.EndsWith("Controller") && + !t.DeriveFrom("Microsoft.AspNetCore.Mvc.Controller".AllowNoMatch()) && + !t.DeriveFrom("Microsoft.AspNetCore.Mvc.ControllerBase".AllowNoMatch()) && + !t.HasAttribute("Microsoft.AspNetCore.Mvc.ControllerAttribute".AllowNoMatch()) +) + +// Select types unused +let typesUnused = + from t in JustMyCode.Types where + t.NbTypesUsingMe == 0 && canTypeBeConsideredAsDeadProc(t) + select t + +// Dead types = types used only by unused types (recursive) +let deadTypesMetric = typesUnused.FillIterative( +types => from t in codeBase.Application.Types.UsedByAny(types).Except(types) + where canTypeBeConsideredAsDeadProc(t) && + t.TypesUsingMe.Intersect(types).Count() == t.NbTypesUsingMe + select t) + +from t in deadTypesMetric.DefinitionDomain +select new { + t, + depth = deadTypesMetric[t], + t.TypesUsingMe, + Debt = 15.ToMinutes().ToDebt(), + AnnualInterest = (10 + (t.NbLinesOfCode ?? 1)).ToMinutes().ToAnnualInterest() +} + +// +// This rule lists *potentially* **dead types**. +// A dead type is a type that can be removed +// because it is never used by the program. +// +// This rule lists not only types not used anywhere in code, +// but also types used only by types not used anywhere in code. +// This is why this rule comes with a column *TypesusingMe* and +// this is why there is a code metric named *depth*: +// +// • A *depth* value of *0* means the type is not used. +// +// • A *depth* value of *1* means the type is used only by types not used. +// +// • etc… +// +// By reading the source code of this rule, you'll see that by default, +// *public* types are not matched, because such type might not be used +// by the analyzed code, but still be used by client code, not analyzed by NDepend. +// This default behavior can be easily changed. +// +// Note that this rule doesn't match Entity Framework ModelSnapshot classes +// that are used ony by the EF infrastructure. +// + +// +// *Static analysis* cannot provide an *exact* list of dead types, +// because there are several ways to use a type *dynamically* (like through reflection). +// +// For each type matched by this query, first investigate if the type is used somehow +// (like through reflection). +// If the type is really never used, it is important to remove it +// to avoid maintaining useless code. +// If you estimate the code of the type might be used in the future, +// at least comment it, and provide an explanatory comment about the future intentions. +// +// If a type is used somehow, +// but still is matched by this rule, you can tag it with the attribute +// **IsNotDeadCodeAttribute** found in *NDepend.API.dll* to avoid matching the type again. +// You can also provide your own attribute for this need, +// but then you'll need to adapt this code rule. +// +// Issues of this rule have a **Debt** equal to 15 minutes because it only +// takes a short while to investigate if a type can be safely discarded. +// The **Annual Interest** of issues of this rule, the annual cost to not +// fix such issue, is proportional to the type #lines of code, because +// the bigger the type is, the more it slows down maintenance. +//]]> + Potentially Dead Methods +// ND1701:PotentiallyDeadMethods + +warnif count > 0 +// Filter procedure for methods that should'nt be considered as dead +let canMethodBeConsideredAsDeadProc = new Func( + m => !m.IsPubliclyVisible && // Public methods might be used by client applications of your assemblies. + !m.IsEntryPoint && // Main() method is not used by-design. + !m.IsExplicitInterfaceImpl && // The IL code never explicitly calls explicit interface methods implementation. + !m.IsClassConstructor && // The IL code never explicitly calls class constructors. + !m.IsFinalizer && // The IL code never explicitly calls finalizers. + !m.IsVirtual && // Only check for non virtual method that are not seen as used in IL. + !(m.IsConstructor && // Don't take account of protected ctor that might be call by a derived ctors. + m.IsProtected) && + !m.IsEventAdder && // The IL code never explicitly calls events adder/remover. + !m.IsEventRemover && + !m.IsGeneratedByCompiler && + !m.ParentType.IsDelegate && + + // Don't consider Global ASP.NET methods as unused + !m.ParentType.DeriveFrom("System.Web.HttpApplication".AllowNoMatch()) && + + // Methods tagged with these attributes are called by the serialization infrastructure. + !m.HasAttribute("System.Runtime.Serialization.OnSerializingAttribute".AllowNoMatch()) && + !m.HasAttribute("System.Runtime.Serialization.OnDeserializingAttribute".AllowNoMatch()) && + !m.HasAttribute("System.Runtime.Serialization.OnSerializedAttribute".AllowNoMatch()) && + !m.HasAttribute("System.Runtime.Serialization.OnDeserializedAttribute".AllowNoMatch()) && + + // If you don't want to link NDepend.API.dll, you can use your own + // IsNotDeadCodeAttribute or UsedImplicitlyAttribute + // and adapt the source code of this rule. + !m.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) && + !m.HasAttribute("JetBrains.Annotations.UsedImplicitlyAttribute".AllowNoMatch()) && + + // Don't consider public getters/setters + // of classes that implement INotifyPropertyChanged + // as dead code. + !(m.IsPublic && (m.IsPropertyGetter || m.IsPropertySetter) && + m.ParentType.Implement("System.ComponentModel.INotifyPropertyChanged".AllowNoMatch())) && + + // ASP.NET MVC Controller classes and methods are used only by the infrastructure + // except methods tagged by NonAction. + !( (m.ParentType.SimpleName.EndsWith("Controller") || + m.ParentType.DeriveFrom("Microsoft.AspNetCore.Mvc.Controller".AllowNoMatch()) || + m.ParentType.DeriveFrom("Microsoft.AspNetCore.Mvc.ControllerBase".AllowNoMatch()) || + m.ParentType.HasAttribute("Microsoft.AspNetCore.Mvc.ControllerAttribute".AllowNoMatch())) && + !m.HasAttribute("System.Web.Mvc.NonActionAttribute".AllowNoMatch())) && + + // Methods of classes that derive from Unity classes are used implicitely by the Unity engine + !m.ParentType.BaseClasses.Any(bc => bc.ParentNamespace.Name.StartsWithAny("UnityEngine","UnityEditor")) + ) + +// Get methods unused +let methodsUnused = + from m in JustMyCode.Methods where + m.NbMethodsCallingMe == 0 && + canMethodBeConsideredAsDeadProc(m) + select m + +// Dead methods = methods used only by unused methods (recursive) +let deadMethodsMetric = methodsUnused.FillIterative( + methods => // Unique loop, just to let a chance to build the hashset. + from o in (new object()).ToEnumerable() + // Use a hashet to make Intersect calls much faster! + let hashset = methods.ToHashSetEx() + from m in codeBase.Application.Methods.UsedByAny(methods).Except(methods) + where canMethodBeConsideredAsDeadProc(m) && + // Select methods called only by methods already considered as dead + hashset.Intersect(m.MethodsCallingMe).Count() == m.NbMethodsCallingMe + select m) + +from m in JustMyCode.Methods.Intersect(deadMethodsMetric.DefinitionDomain) +let depth = deadMethodsMetric[m] +select new { + m, + depth, + m.MethodsCallingMe, + Debt = (10 + 3*depth).ToMinutes().ToDebt(), + AnnualInterest = (8 + (m.NbLinesOfCode ?? 1)).ToMinutes().ToAnnualInterest() +} + +// +// This rule lists *potentially* **dead methods**. +// A dead method is a method that can be removed +// because it is never called by the program. +// +// This rule lists not only methods not called anywhere in code, +// but also methods called only by methods not called anywhere in code. +// This is why this rule comes with a column *MethodsCallingMe* and +// this is why there is a code metric named *depth*: +// +// • A *depth* value of *0* means the method is not called. +// +// • A *depth* value of *1* means the method is called only by methods not called. +// +// • etc… +// +// By reading the source code of this rule, you'll see that by default, +// *public* methods are not matched, because such method might not be called +// by the analyzed code, but still be called by client code, not analyzed by NDepend. +// This default behavior can be easily changed. +// + +// +// *Static analysis* cannot provide an *exact* list of dead methods, +// because there are several ways to invoke a method *dynamically* (like through reflection). +// +// For each method matched by this query, first investigate if the method is invoked somehow +// (like through reflection). +// If the method is really never invoked, it is important to remove it +// to avoid maintaining useless code. +// If you estimate the code of the method might be used in the future, +// at least comment it, and provide an explanatory comment about the future intentions. +// +// If a method is invoked somehow, +// but still is matched by this rule, you can tag it with the attribute +// **IsNotDeadCodeAttribute** found in *NDepend.API.dll* to avoid matching the method again. +// You can also provide your own attribute for this need, +// but then you'll need to adapt this code rule. +// +// Issues of this rule have a **Debt** equal to 10 minutes because it only +// takes a short while to investigate if a method can be safely discarded. +// On top of these 10 minutes, the depth of usage of such method adds up +// 3 minutes per unity because dead method only called by dead code +// takes a bit more time to be investigated. +// +// The **Annual Interest** of issues of this rule, the annual cost to not +// fix such issue, is proportional to the type #lines of code, because +// the bigger the method is, the more it slows down maintenance. +//]]> + Potentially Dead Fields +// ND1702:PotentiallyDeadFields + +warnif count > 0 +from f in JustMyCode.Fields where + f.NbMethodsUsingMe == 0 && + !f.IsPublic && // Although not recommended, public fields might be used by client applications of your assemblies. + !f.IsLiteral && // The IL code never explicitly uses literal fields. + !f.IsEnumValue && // The IL code never explicitly uses enumeration value. + f.Name != "value__" && // Field named 'value__' are relative to enumerations and the IL code never explicitly uses them. + !f.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) && + !f.HasAttribute("JetBrains.Annotations.UsedImplicitlyAttribute".AllowNoMatch()) && + !f.IsGeneratedByCompiler && + // If you don't want to link NDepend.API.dll, you can use your own IsNotDeadCodeAttribute + // and adapt the source code of this rule. + + // Don't warn for fields tagged with these Unity specific attribute + !f.HasAttribute("UnityEngine.SerializeField".AllowNoMatch()) && + !f.HasAttribute("UnityEngine.SerializeReference".AllowNoMatch()) + +select new { + f, + Debt = 10.ToMinutes().ToDebt(), + AnnualInterest = 8.ToMinutes().ToAnnualInterest() +} + +// +// This rule lists *potentially* **dead fields**. +// A dead field is a field that can be removed +// because it is never used by the program. +// +// By reading the source code of this rule, you'll see that by default, +// *public* fields are not matched, because such field might not be used +// by the analyzed code, but still be used by client code, not analyzed by NDepend. +// This default behavior can be easily changed. +// Some others default rules in the *Visibility* group, warn about public fields. +// +// More restrictions are applied by this rule because of some *by-design* limitations. +// NDepend mostly analyzes compiled IL code, and the information that +// an enumeration value or a literal constant (which are fields) is used +// is lost in IL code. Hence by default this rule won't match such field. +// + +// +// *Static analysis* cannot provide an *exact* list of dead fields, +// because there are several ways to assign or read a field *dynamically* +// (like through reflection). +// +// For each field matched by this query, first investigate +// if the field is used somehow (like through reflection). +// If the field is really never used, it is important to remove it +// to avoid maintaining a useless code element. +// +// If a field is used somehow, +// but still is matched by this rule, you can tag it with the attribute +// **IsNotDeadCodeAttribute** found in *NDepend.API.dll* +// to avoid matching the field again. +// You can also provide your own attribute for this need, +// but then you'll need to adapt this code rule. +// +// Issues of this rule have a **Debt** equal to 10 minutes because it only +// takes a short while to investigate if a method can be safely discarded. +// The **Annual Interest** of issues of this rule, the annual cost to not +// fix such issue, is set by default to 8 minutes per unused field matched. +//]]> + Wrong usage of IsNotDeadCodeAttribute +// ND1703:WrongUsageOfIsNotDeadCodeAttribute + +warnif count > 0 + +let tAttr = Types.WithFullName("NDepend.Attributes.IsNotDeadCodeAttribute").FirstOrDefault() +where tAttr != null + +// Get types that do a wrong usage of IsNotDeadCodeAttribute +let types = from t in Application.Types where + t.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) && + + ( // types used don't need to be tagged with IsNotDeadCodeAttribute! + t.TypesUsingMe.Count(t1 => + !t.NestedTypes.Contains(t1) && + !t1.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) ) > 0 || + + // Static types that define only const fields cannot be seen as used in IL code. + // They don't need to be tagged with IsNotDeadCodeAttribute. + (t.IsStatic && t.NbMethods == 0 && !t.Fields.Where(f => !f.IsLiteral).Any()) + ) + select t + +// Get methods that do a wrong usage of IsNotDeadCodeAttribute +let methods = from m in Application.Methods where + m.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) && + m.MethodsCallingMe.Count(m1 => !m1.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch())) > 0 + select m + +// Get fields that do a wrong usage of IsNotDeadCodeAttribute +let fields = from f in Application.Fields where + f.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) && + f.MethodsUsingMe.Count(m1 => !m1.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch())) > 0 + select f + +from member in types.Cast().Concat(methods).Concat(fields) +select new { + member, + Debt = 4.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// The attribute **NDepend.Attributes.IsNotDeadCodeAttribute** +// is defined in *NDepend.API.dll*. This attribute is used +// to mean that a code element is not used directly, but is used +// somehow, like through reflection. +// +// This attribute is used in the dead code rules, +// *Potentially dead Types*, *Potentially dead Methods* +// and *Potentially dead Fields*. +// If you don't want to link *NDepend.API.dll*, you can use +// your own *IsNotDeadCodeAttribute* and adapt the source code of +// this rule, and the source code of the *dead code* rules. +// +// In this context, this code rule matches code elements +// (types, methods, fields) that are tagged with this attribute, +// but still used directly somewhere in the code. +// + +// +// Just remove *IsNotDeadCodeAttribute* tagging of +// types, methods and fields matched by this rule +// because this tag is not useful anymore. +//]]> + + + Don't use CoSetProxyBlanket and CoInitializeSecurity +// ND3100:DontUseCoSetProxyBlanketAndCoInitializeSecurity +warnif count > 0 + +from m in Application.Methods +where (m.HasAttribute ("System.Runtime.InteropServices.DllImportAttribute".AllowNoMatch())) + && m.SimpleName.EqualsAny("CoSetProxyBlanket","CoInitializeSecurity") + +select new { + m, + Debt = 1.ToHours().ToDebt(), + AnnualInterest = 2.ToHours().ToAnnualInterest() +} + +// +// As soon as some managed code starts being JIT’ed and executed by the CLR, +// it is too late to call *CoInitializeSecurity()* or *CoSetProxyBlanket()*. +// By this point in time, the CLR has already initialized the +// COM security environment for the entire Windows process. +// + +// +// Don't call CoSetProxyBlanket() or CoInitializeSecurity() from managed code. +// +// Instead write an unmanaged "shim" in C++ that will call +// one or both methods before loading the CLR within the process. +// +// More information about writing such unmanaged "shim" +// can be found in this StackOverflow answer: +// https://stackoverflow.com/a/48545055/27194 +//]]> + Don't use System.Random for security purposes +// ND3101:DontUseSystemRandomForSecurityPurposes +warnif count > 0 + +from m in Application.Methods +where m.CreateA("System.Random".AllowNoMatch()) +select new { + m, + Debt = 15.ToMinutes().ToDebt(), + AnnualInterest = 1.ToHours().ToAnnualInterest() +} + +// +// The algorithm used by the implementation of **System.Random** is weak +// because random numbers generated can be predicted. +// +// Using predictable random values in a security critical context +// can lead to vulnerabilities. +// + +// +// If the matched method is meant to be executed in a security +// critical context use **System.Security.Cryptography.RandomNumberGenerator** +// or **System.Security.Cryptography.RNGCryptoServiceProvider** instead. +// These random implementations are slower to execute but the random numbers +// generated cannot be predicted. +// +// Find more on using *RNGCryptoServiceProvider* to generate random values here: +// https://stackoverflow.com/questions/32932679/using-rngcryptoserviceprovider-to-generate-random-string +// +// Otherwise you can use the faster **System.Random** implementation and +// suppress corresponding issues. +// +// More information about the weakness of *System.Random* implementation +// can be found here: https://stackoverflow.com/a/6842191/27194 +//]]> + Don't use DES/3DES weak cipher algorithms +// ND3102:DontUseDES3DESWeakCipherAlgorithms +warnif count > 0 + +from m in Application.Methods +where + m.CreateA("System.Security.Cryptography.TripleDESCryptoServiceProvider".AllowNoMatch()) || + m.CreateA("System.Security.Cryptography.DESCryptoServiceProvider".AllowNoMatch()) || + m.IsUsing("System.Security.Cryptography.DES.Create()".AllowNoMatch()) || + m.IsUsing("System.Security.Cryptography.DES.Create(String)".AllowNoMatch()) +select new { + m, + Debt = 1.ToHours().ToDebt(), + Severity = Severity.High +} + +// +// Since 2005 the NIST, the US National Institute of Standards and Technology, +// doesn't consider DES and 3DES cypher algorithms as secure. Source: +// https://www.nist.gov/news-events/news/2005/06/nist-withdraws-outdated-data-encryption-standard +// + +// +// Use the AES (Advanced Encryption Standard) algorithms instead +// with the .NET Framework implementation: +// *System.Security.Cryptography.AesCryptoServiceProvider*. +// +// You can still suppress issues of this rule when using +// DES/3DES algorithms for compatibility reasons with legacy applications and data. +//]]> + Don't disable certificate validation +// ND3103:DontDisableCertificateValidation +warnif count > 0 + +from m in Application.Methods +where + m.IsUsing("System.Net.ServicePointManager.set_ServerCertificateValidationCallback(RemoteCertificateValidationCallback)".AllowNoMatch()) +select new { + m, + Debt = 30.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// Matched methods are subscribing a custom certificate validation +// procedure through the delegate: *ServicePointManager.ServerCertificateValidationCallback*. +// +// Doing so is often used to disable certificate validation +// to connect easily to a host that is not signed by a **root certificate authority**. +// https://en.wikipedia.org/wiki/Root_certificate +// +// This creates a **vulnerability to man-in-the-middle attacks** since the client will trust any certificate. +// https://en.wikipedia.org/wiki/Man-in-the-middle_attack +// + +// +// Don't rely on a weak custom certificate validation. +// +// If a legitimate custom certificate validation procedure must be subscribed, +// you can chose to suppress related issue(s). +//]]> + Review publicly visible event handlers +// ND3104:ReviewPubliclyVisibleEventHandlers +warnif count > 0 +from m in Application.Methods +where + m.IsPubliclyVisible + && m.Name.EndsWith("(Object,EventArgs)") +select new { + m, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Low +} + + +// +// Review publicly visible event handlers to check those that are +// running security critical actions. +// +// An event handler is any method with the standard signature *(Object,EventArgs)*. +// An event handler can be registered to any event matching this standard signature. +// +// As a consequence, such event handler can be subscribed +// by malicious code to an event to provoke execution of the +// security critical action on event firing. +// +// Even if such event handler does a security check, +// it can be executed from a chain of trusted callers on the call stack, +// and cannot detect about malicious registration. +// + +// +// Change matched event handlers to make them non-public. +// Preferably don't run a security critical action from an event handler. +// +// If after a careful check no security critical action is involved +// from a matched event-handler, you can suppress the issue. +//]]> + Pointers should not be publicly visible +// ND3105:PointersShouldNotBePubliclyVisible + +warnif count > 0 +from f in Application.Fields +where + f.FieldType != null && + f.FieldType.FullName.EqualsAny("System.IntPtr","System.UIntPtr") && + (f.IsPubliclyVisible || f.IsProtected) +let methodsUserOutsideMyAssembly = f.MethodsUsingMe.Where(m => m.ParentAssembly != m.ParentAssembly) +select new { + f, + f.FieldType, + methodsUserOutsideMyAssembly, + Debt = (15 + 10*methodsUserOutsideMyAssembly.Count()).ToMinutes().ToDebt(), + Severity = f.IsInitOnly ? Severity.Medium : Severity.High +} + +// +// Pointers should not be exposed publicly. +// +// This rule detects fields with type *System.IntPtr* or *System.UIntPtr* +// that are public or protected. +// +// Pointers are used to access unmanaged memory from managed code. +// Exposing a pointer publicly makes it easy for malicious code +// to read and write unmanaged data used by the application. +// +// The situation is even worse if the field is not read-only +// since malicious code can change it and force the application +// to rely on arbitrary data. +// + +// +// Pointers should have the visibility - private or internal. +// +// The estimated Debt, which means the effort to fix such issue, +// is 15 minutes and 10 additional minutes per method using the field outside its assembly. +// +// The estimated Severity of such issue is *Medium*, and *High* +// if the field is non read-only. +//]]> + Seal methods that satisfy non-public interfaces +// ND3106:SealMethodsThatSatisfyNonPublicInterfaces + +warnif count > 0 +from m in Application.Methods +where + !m.IsFinal && // If the method is not marked as virtual the flag final is set + m.IsPubliclyVisible && + !m.ParentType.IsSealed && + m.ParentType.IsClass +let overridenInterface = + m.OverriddensBase.FirstOrDefault(mb => mb.ParentType.IsInterface && !mb.ParentType.IsPubliclyVisible) +where overridenInterface != null +select new { + m, + overridenInterface = overridenInterface.ParentType, + Debt = 30.ToMinutes().ToDebt(), + Severity = Severity.High + } + +// +// A match of this rule represents a virtual method, publicly visible, +// defined in a non-sealed public class, that overrides a method of an +// internal interface. +// +// The interface not being public indicates a process that +// should remain private. +// +// Hence this situation represents a security vulnerability because +// it is now possible to create a malicious class, that derives from +// the parent class and that overrides the method behavior. +// This malicious behavior will be then invoked by private implementation. +// + +// +// You can: +// +// - seal the parent class, +// +// - or change the accessibility of the parent class to non-public, +// +// - or implement the method without using the *virtual* modifier, +// +// - or change the accessibility of the method to non-public. +// +// If after a careful check such situation doesn't represent +// a security threat, you can suppress the issue. +//]]> + Review commands vulnerable to SQL injection +// ND3107:ReviewCommandsVulnerableToSQLInjection + +warnif count > 0 + +let commands = ThirdParty.Types.WithFullNameIn( + "System.Data.Common.DbCommand", + "System.Data.SqlClient.SqlCommand", + "System.Data.OleDb.OleDbCommand", + "System.Data.Odbc.OdbcCommand", + "System.Data.OracleClient.OracleCommand") +where commands.Any() +let commandCtors = commands.ChildMethods().Where(m => m.IsConstructor).ToHashSetEx() +let commandExecutes = commands.ChildMethods().Where(m => m.SimpleName.Contains("Execute")).ToHashSetEx() +let commandParameters = commands.ChildMethods().Where(m => m.IsPropertyGetter && m.SimpleName == "get_Parameters").ToHashSetEx() + + +from m in Application.Methods.UsingAny(commandCtors) + .UsingAny(commandExecutes) +where !m.MethodsCalled.Intersect(commandParameters).Any() && + // Check also that non of the method call rely on command parameters to get less false positives: + !m.MethodsCalled.SelectMany(mc => mc.MethodsCalled).Intersect(commandParameters).Any() + +select new { m, + createA = m.MethodsCalled.Intersect(commandCtors).First().ParentType, + calls = m.MethodsCalled.Intersect(commandExecutes).First(), + Debt = 15.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule matches methods that create a DB command +// (like an *SqlCommand* or an *OleDbCommand*) +// that call an *Execute* command method (like *ExecuteScalar()*) +// and that don't use command parameters. +// +// This situation is prone to **SQL Injection** https://en.wikipedia.org/wiki/SQL_injection +// since malicious SQL code might be injected in string parameters values. +// +// However there might be false positives. +// So review carefully matched methods +// and use suppress issues when needed. +// +// To limit the false positives, this rule also checks whether +// command parameters are accessed from any sub method call. +// This is a solid indication of non-vulnerability. +// + +// +// If after a careful check it appears that the method is indeed +// using some strings to inject parameters values in the SQL query string, +// **command.Parameters.Add(...)** must be used instead. +// +// You can get more information on adding parameters explicitely here: +// https://stackoverflow.com/questions/4892166/how-does-sqlparameter-prevent-sql-injection +// ]]> + Review data adapters vulnerable to SQL injection +// ND3108:ReviewDataAdaptersVulnerableToSQLInjection + +warnif count > 0 + +let adapters = ThirdParty.Types.WithFullNameIn( + "System.Data.Common.DataAdapter", + "System.Data.Common.DbDataAdapter", + "System.Data.SqlClient.SqlDataAdapter", + "System.Data.OleDb.OleDbDataAdapter", + "System.Data.Odbc.OdbcDataAdapter", + "System.Data.OracleClient.OracleDataAdapter") + +where adapters.Any() +let adapterCtors = adapters.ChildMethods().Where(m => m.IsConstructor).ToHashSetEx() +let adapterFill = adapters.ChildMethods().Where(m => m.SimpleName.Contains("Fill")).ToHashSetEx() + + +let commands = ThirdParty.Types.WithFullNameIn( + "System.Data.Common.DbCommand", + "System.Data.SqlClient.SqlCommand", + "System.Data.OleDb.OleDbCommand", + "System.Data.Odbc.OdbcCommand", + "System.Data.OracleClient.OracleCommand") +where commands.Any() +let commandParameters = commands.ChildMethods().Where(m => m.IsPropertyGetter && m.SimpleName == "get_Parameters").ToHashSetEx() + + +from m in Application.Methods.UsingAny(adapterCtors) + .UsingAny(adapterFill) +where !m.MethodsCalled.Intersect(commandParameters).Any() && + // Check also that non of the method call rely on command parameters to get less false positives: + !m.MethodsCalled.SelectMany(mc => mc.MethodsCalled).Intersect(commandParameters).Any() + +select new { m, + createA = m.MethodsCalled.Intersect(adapterCtors).First().ParentType, + calls = m.MethodsCalled.Intersect(adapterFill).First(), + Debt = 15.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule matches methods that create a DB adapter +// (like an *SqlDataAdapter* or an *OleDbDataAdapter*) +// that call the method *Fill()* +// and that don't use DB command parameters. +// +// This situation is prone to **SQL Injection** https://en.wikipedia.org/wiki/SQL_injection +// since malicious SQL code might be injected in string parameters values. +// +// However there might be false positives. +// So review carefully matched methods +// and use suppress issues when needed. +// +// To limit the false positives, this rule also checks whether +// command parameters are accessed from any sub method call. +// This is a solid indication of non-vulnerability. +// + +// +// If after a careful check it appears that the method is indeed +// using some strings to inject parameters values in the SQL query string, +// **adapter.SelectCommand.Parameters.Add(...)** must be used instead +// (or *adapter.UpdateCommand* or *adapter.InsertCommand*, depending on the context). +// +// You can get more information on adding parameters explicitely here: +// https://stackoverflow.com/questions/4892166/how-does-sqlparameter-prevent-sql-injection +// ]]> + + + Methods that could have a lower visibility +// ND1800:MethodsThatCouldHaveALowerVisibility + +warnif count > 0 from m in JustMyCode.Methods where + m.Visibility != m.OptimalVisibility && + + !m.HasAttribute("NDepend.Attributes.CannotDecreaseVisibilityAttribute".AllowNoMatch()) && + !m.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) && + !m.HasAttribute("JetBrains.Annotations.UsedImplicitlyAttribute".AllowNoMatch()) && + // If you don't want to link NDepend.API.dll, you can use your own attributes + // and adapt the source code of this rule. + + // methods of serializable type must remain public. + !m.ParentType.TypesUsed.Any(t1 => t1.IsAttributeClass && t1.ParentNamespace.Name == "Newtonsoft.Json") && + !m.ParentType.HasAttribute("System.Runtime.Serialization.DataContractAttribute".AllowNoMatch()) && + !m.ParentType.HasAttribute("System.Xml.Serialization.XmlRootAttribute".AllowNoMatch()) && + !m.ParentType.HasAttribute("System.SerializableAttribute".AllowNoMatch()) && + + // ASP.NET MVC Controller classes and methods are used only by the infrastructure and must be public + !m.ParentType.SimpleName.EndsWith("Controller") && + !m.ParentType.DeriveFrom("Microsoft.AspNetCore.Mvc.Controller".AllowNoMatch()) && + !m.ParentType.DeriveFrom("Microsoft.AspNetCore.Mvc.ControllerBase".AllowNoMatch()) && + !m.ParentType.HasAttribute("Microsoft.AspNetCore.Mvc.ControllerAttribute".AllowNoMatch()) && + + // Eliminate public methods visible outside of their assembly + // because the rule cannot determine if the developer left the method public + // intentionally or not. + !m.IsPubliclyVisible && + + // Avoid matching public methods declared in a non-public type, + // that could have the visibility internal, because + // such situation is caught by the rule 'Avoid public methods not publicly visible'. + !(m.Visibility == Visibility.Public && + m.ParentType.Visibility != Visibility.Public && + m.OptimalVisibility == Visibility.Internal) && + + // Eliminate default constructor from the result. + // Whatever the visibility of the declaring class, + // default constructors are public and introduce noise + // in the current rule. + !( m.IsConstructor && m.IsPublic && m.NbParameters == 0) && + + // Don't advise to reduce visibility of property getters/setters + // of classes that implement INotifyPropertyChanged + !((m.IsPropertyGetter || m.IsPropertySetter) && + m.ParentType.Implement("System.ComponentModel.INotifyPropertyChanged".AllowNoMatch())) && + + // Don't decrease the visibility of Main() methods. + !m.IsEntryPoint + +select new { + m, + m.Visibility , + CouldBeDeclared = m.OptimalVisibility, + m.MethodsCallingMe, + + Debt = 30.ToSeconds().ToDebt(), // It is fast to change the method visibility + Severity = Severity.Medium +} + +// +// This rule warns about methods that can be declared with a lower visibility +// without provoking any compilation error. +// For example *private* is a visibility lower than *internal* +// which is lower than *public*. +// +// **Narrowing visibility** is a good practice because doing so **promotes encapsulation**. +// The scope from which methods can be called is then reduced to a minimum. +// +// By default, this rule doesn't match publicly visible methods that could have a +// lower visibility because it cannot know if the developer left the method public +// intentionally or not. Public methods matched are declared in non-public types. +// +// By default this rule doesn't match methods with the visibility *public* +// that could be *internal*, declared in a type that is not *public* +// (internal, or nested private for example) because +// this situation is caught by the rule *Avoid public methods not publicly visible*. +// +// Notice that methods tagged with one of the attribute +// *NDepend.Attributes.CannotDecreaseVisibilityAttribute* or +// *NDepend.Attributes.IsNotDeadCodeAttribute*, found in *NDepend.API.dll* +// are not matched. If you don't want to link *NDepend.API.dll* but still +// wish to rely on this facility, you can declare these attributes in your code. +// + +// +// Declare each matched method with the specified *optimal visibility* +// in the *CouldBeDeclared* rule result column. +// +// By default, this rule matches *public methods*. If you are publishing an API +// many public methods matched should remain public. In such situation, +// you can opt for the *coarse solution* to this problem by adding in the +// rule source code *&& !m.IsPubliclyVisible* or you can prefer the +// *finer solution* by tagging each concerned method with +// *CannotDecreaseVisibilityAttribute*. +//]]> + Types that could have a lower visibility +// ND1801:TypesThatCouldHaveALowerVisibility + +warnif count > 0 from t in JustMyCode.Types where + + t.Visibility != t.OptimalVisibility && + + // If you don't want to link NDepend.API.dll, you can use your own attributes + // and adapt the source code of this rule. + !t.HasAttribute("NDepend.Attributes.CannotDecreaseVisibilityAttribute".AllowNoMatch()) && + !t.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) && + !t.HasAttribute("JetBrains.Annotations.UsedImplicitlyAttribute".AllowNoMatch()) && + + // JSON, XML and binary serialized type must remain public. + !t.IsUsing("Newtonsoft.Json".MatchNamespace().AllowNoMatch()) && + !t.HasAttribute("System.Xml.Serialization.XmlRootAttribute".AllowNoMatch()) && + !t.HasAttribute("System.SerializableAttribute".AllowNoMatch()) && + + // Eliminate public types visible outside of their assembly + // because the rule cannot know if the developer left the type public + // intentionally or not. + !t.IsPubliclyVisible && + + // Static types that define only const fields cannot be seen as used in IL code. + // They don't have to be tagged with CannotDecreaseVisibilityAttribute. + !( t.IsStatic && + !t.Methods.Any(m => !m.IsClassConstructor) && + !t.Fields.Any(f => !f.IsLiteral && !(f.IsStatic && f.IsInitOnly))) && + + // A type used by an interface that has the same visibility + // cannot have its visibility decreased, else a compilation error occurs! + !t.TypesUsingMe.Any(tUser => + tUser.IsInterface && + tUser.Visibility == t.Visibility) && + + // Don't change the visibility of a type that contain an entry point method. + !t.Methods.Any(m =>m.IsEntryPoint) + +select new { + t, + t.Visibility , + CouldBeDeclared = t.OptimalVisibility, + t.TypesUsingMe, + + Debt = 30.ToSeconds().ToDebt(), // It is fast to change the method visibility + Severity = Severity.Medium +} + +// +// This rule warns about types that can be declared with a lower visibility +// without provoking any compilation error. +// For example *private* is a visibility lower than *internal* +// which is lower than *public*. +// +// **Narrowing visibility** is a good practice because doing so **promotes encapsulation**. +// The scope from which types can be consumed is then reduced to a minimum. +// +// By default, this rule doesn't match publicly visible types that could have +// a lower visibility because it cannot know if the developer left the type public +// intentionally or not. Public types matched are nested in non-public types. +// +// Notice that types tagged with one of the attribute +// *NDepend.Attributes.CannotDecreaseVisibilityAttribute* or +// *NDepend.Attributes.IsNotDeadCodeAttribute*, found in *NDepend.API.dll* +// are not matched. If you don't want to link *NDepend.API.dll* but still +// wish to rely on this facility, you can declare these attributes in your code. +// + +// +// Declare each matched type with the specified *optimal visibility* +// in the *CouldBeDeclared* rule result column. +// +// By default, this rule matches *public types*. If you are publishing an API +// many public types matched should remain public. In such situation, +// you can opt for the *coarse solution* to this problem by adding in the +// rule source code *&& !m.IsPubliclyVisible* or you can prefer the +// *finer solution* by tagging each concerned type with +// *CannotDecreaseVisibilityAttribute*. +//]]> + Fields that could have a lower visibility +// ND1802:FieldsThatCouldHaveALowerVisibility + +warnif count > 0 from f in JustMyCode.Fields where + f.Visibility != f.OptimalVisibility && + + // Eliminate public fields visible outside of their assembly + // because the rule cannot know if the developer left the field public + // intentionally or not. + !f.IsPubliclyVisible && + + !f.HasAttribute("NDepend.Attributes.CannotDecreaseVisibilityAttribute".AllowNoMatch()) && + !f.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) && + !f.HasAttribute("JetBrains.Annotations.UsedImplicitlyAttribute".AllowNoMatch()) && + // If you don't want to link NDepend.API.dll, you can use your own attributes + // and adapt the source code of this rule. + + // JSON and XML serialized fields must remain public. + !f.ParentType.TypesUsed.Any(t1 => t1.IsAttributeClass && t1.ParentNamespace.Name == "Newtonsoft.Json") && + !f.HasAttribute("System.Xml.Serialization.XmlElementAttribute".AllowNoMatch()) && + !f.HasAttribute("System.Xml.Serialization.XmlAttributeAttribute".AllowNoMatch()) && + !f.HasAttribute("System.Xml.Serialization.XmlArrayAttribute".AllowNoMatch()) && + !f.HasAttribute("System.Xml.Serialization.XmlArrayItemAttribute".AllowNoMatch()) && + + // Don't check for serialized fields visibility + !f.HasAttribute("System.Runtime.Serialization.DataMemberAttribute".AllowNoMatch()) && + + // Fields of classes that derive from Unity classes might be used implicitely by the Unity engine + !f.ParentType.BaseClasses.Any(bc => bc.ParentNamespace.Name.StartsWithAny("UnityEngine","UnityEditor")) + +select new { + f, + f.Visibility , + CouldBeDeclared = f.OptimalVisibility, + f.MethodsUsingMe, + + Debt = 30.ToSeconds().ToDebt(), // It is fast to change the field visibility + Severity = Severity.Medium +} + +// +// This rule warns about fields that can be declared with a lower visibility +// without provoking any compilation error. +// For example *private* is a visibility lower than *internal* +// which is lower than *public*. +// +// **Narrowing visibility** is a good practice because doing so **promotes encapsulation**. +// The scope from which fields can be consumed is then reduced to a minimum. +// +// By default, this rule doesn't match publicly visible fields that could have a +// lower visibility because it cannot know if the developer left the field public +// intentionally or not. Public fields matched are declared in non-public types. +// +// Notice that fields tagged with one of the attribute +// *NDepend.Attributes.CannotDecreaseVisibilityAttribute* or +// *NDepend.Attributes.IsNotDeadCodeAttribute*, found in *NDepend.API.dll* +// are not matched. If you don't want to link *NDepend.API.dll* but still +// wish to rely on this facility, you can declare these attributes in your code. +// + +// +// Declare each matched field with the specified *optimal visibility* +// in the *CouldBeDeclared* rule result column. +// +// By default, this rule matches *public fields*. If you are publishing an API +// some public fields matched should remain public. In such situation, +// you can opt for the *coarse solution* to this problem by adding in the +// rule source code *&& !m.IsPubliclyVisible* or you can prefer the +// *finer solution* by tagging eah concerned field with +// *CannotDecreaseVisibilityAttribute*. +//]]> + Types that could be declared as private, nested in a parent type +// ND1803:TypesThatCouldBeDeclaredAsPrivateNestedInAParentType + +warnif count > 0 +from t in JustMyCode.Types +where !t.IsGeneratedByCompiler && + !t.IsNested && + !t.IsPubliclyVisible && + !t.IsEnumeration && + // Only one type user… + t.TypesUsingMe.Count() == 1 + +let couldBeNestedIn = t.TypesUsingMe.Single() +where !couldBeNestedIn.IsGeneratedByCompiler && + !couldBeNestedIn.IsInterface && // Cannot nest a type in an interface + // …declared in the same namespace + couldBeNestedIn.ParentNamespace == t.ParentNamespace && + + // Don't advise to move a base class + // or an interface into one of its child type. + !couldBeNestedIn.DeriveFrom(t) && + !couldBeNestedIn.Implement(t) + + // Require that t doesn't contain any extension method. + // Types with extension methods cannot be nested. +where t.Methods.All(m => !m.IsExtensionMethod) + +select new { + t, + couldBeNestedIn, + Debt = 3.ToMinutes().ToDebt(), // It is fast to nest a type into another one + Severity = Severity.Low // This rule proposes advices, not potential problems +} + +// +// This rule matches types that can be potentially +// *nested* and declared *private* into another type. +// +// The conditions for a type to be potentially nested +// into a *parent type* are: +// +// • the *parent type* is the only type consuming it, +// +// • the type and the *parent type* are declared in the same namespace. +// +// Declaring a type as private into a parent type **promotes encapsulation**. +// The scope from which the type can be consumed is then reduced to a minimum. +// +// This rule doesn't match classes with extension methods +// because such class cannot be nested in another type. +// + +// +// Nest each matched *type* into the specified *parent type* and +// declare it as private. +// +// However *nested private types* are hardly testable. Hence this rule +// might not be applied to types consumed directly by tests. +//]]> + Avoid publicly visible constant fields +// ND1804:AvoidPubliclyVisibleConstantFields + +warnif count > 0 +from f in JustMyCode.Fields +where f.IsLiteral && + f.IsPubliclyVisible && + !f.IsEnumValue +select new { + f, + + Debt = 30.ToSeconds().ToDebt(), // It is fast to update field declaration + Severity = Severity.Medium +} + +// +// This rule warns about constant fields that are visible outside their +// parent assembly. Such field, when used from outside its parent assembly, +// has its constant value *hard-coded* into the client assembly. +// Hence, when changing the field's value, it is *mandatory* to recompile +// all assemblies that consume the field, else the program will run +// with different constant values in-memory. Certainly in such situation +// bugs are lurking. +// + +// +// Declare matched fields as **static readonly** instead of **constant**. +// This way, the field value is *safely changeable* without the need to +// recompile client assemblies. +// +// Notice that enumeration value fields suffer from the same *potential +// pitfall*. But enumeration values cannot be declared as +// *static readonly* hence the rule comes with the condition +// **&& !f.IsEnumValue** to avoid matching these. Unless you decide +// to banish public enumerations, just let the rule *as is*. +//]]> + Fields should be declared as private +// ND1805:FieldsShouldBeDeclaredAsPrivate + +warnif count > 0 +let isUnityProject = ThirdParty.Assemblies.Any(a => a.Name.StartsWith("UnityEngine")) +from f in JustMyCode.Fields where + // In Unity projects often there is no properties for high-performance reasons + // and public fields are used instead. + !isUnityProject && + !f.IsPrivate && + + // These conditions filter cases where fields + // doesn't represent state that should be encapsulated. + !f.IsGeneratedByCompiler && + !f.IsSpecialName && + !f.IsInitOnly && + !f.IsLiteral && + !f.IsEnumValue && + !f.HasAttribute("System.Xml.Serialization.XmlAttributeAttribute".AllowNoMatch()) && + !f.HasAttribute("System.Runtime.Serialization.DataMemberAttribute".AllowNoMatch()) + +// A non-private field assigned from outside its class, +// usually leads to complicated field state management. +let outsideMethodsAssigningMe = + f.MethodsAssigningMe.Where(m => m.ParentType != f.ParentType) + +select new { + f, + f.Visibility, + outsideMethodsAssigningMe, + + Debt = (60+20*outsideMethodsAssigningMe.Count()).ToSeconds().ToDebt(), + // The cost to leave such issue unfixed is higher if the field is publicly visible! + AnnualInterest = Severity.Medium.AnnualInterestThreshold() * (f.IsPubliclyVisible ? 3 : 1) +} + +// +// This rule matches **non-private and mutable fields**. +// *Mutable* means that the field value can be modified. +// Typically mutable fields are *non-constant*, +// *non-readonly* fields. +// +// Fields should be considered as **implementation details** +// and as a consequence they should be declared as private. +// +// If something goes wrong with a *non-private field*, +// the culprit can be anywhere, and so in order to track down +// the bug, you may have to look at quite a lot of code. +// +// A private field, by contrast, can only be assigned from +// inside the same class, so if something goes wrong with that, +// there is usually only one source file to look at. +// +// Issues of this rule are fast to get fixed, and they have +// a debt proportional to the number of methods assigning +// the field. +// + +// +// Declare a matched mutable field as *private*, or declare it +// as *readonly*. +// +// If code outside the type needs to access the field +// you can encapsulate the field accesses in a read-write property. +// At least with a read-write property you can set a debug breakpoint +// on the property setter, which makes easier to track write-accesses +// in case of problem. +// +]]> + Constructors of abstract classes should be declared as protected or private +// ND1806:ConstructorsOfAbstractClassesShouldBeDeclaredAsProtectedOrPrivate + +warnif count > 0 +from t in Application.Types where + t.IsClass && + t.IsAbstract +let ctors = t.Constructors.Where(c => !c.IsProtected && !c.IsPrivate) +where ctors.Count() > 0 +select new { + t, + ctors, + + Debt = 30.ToSeconds().ToDebt(), + Severity = Severity.Medium +} + +// +// Constructors of abstract classes can only be called from derived +// classes. +// +// Because a public constructor is creating instances of its class, +// and because it is forbidden to create instances of an *abstract* class, +// an abstract class with a public constructor is wrong design. +// +// Notice that when the constructor of an abstract class is private, +// it means that derived classes must be nested in the abstract class. +// + +// +// To fix a violation of this rule, +// either declare the constructor as *protected*, +// or do not declare the type as *abstract*. +//]]> + Avoid public methods not publicly visible +// ND1807:AvoidPublicMethodsNotPubliclyVisible + +warnif count > 0 +from m in JustMyCode.Methods where + !m.IsPubliclyVisible && m.IsPublic && + + // Eliminate virtual methods + !m.IsVirtual && + // Eliminate interface and delegate types + !m.ParentType.IsInterface && + !m.ParentType.IsDelegate && + // Eliminate default constructors + !(m.IsConstructor && m.NbParameters == 0) && + // Eliminate operators that must be declared public + !m.IsOperator && + // Eliminate methods generated by compiler, except auto-property getter/setter + (!m.IsGeneratedByCompiler || m.IsPropertyGetter || m.IsPropertySetter) && + + // Don't advise to reduce visibility of property getters/setters + // of classes that implement INotifyPropertyChanged + !((m.IsPropertyGetter || m.IsPropertySetter) && + m.ParentType.Implement("System.ComponentModel.INotifyPropertyChanged".AllowNoMatch())) + +let calledOutsideParentType = + m.MethodsCallingMe.FirstOrDefault(mCaller => mCaller.ParentType != m.ParentType) != null + +select new { + m, + parentTypeVisibility = m.ParentType.Visibility, + declareMethodAs = (Visibility) (calledOutsideParentType ? Visibility.Internal : Visibility.Private), + methodsCaller = m.MethodsCallingMe, + + Debt = 30.ToSeconds().ToDebt(), + Severity = Severity.Low +} + +// +// This rule warns about methods declared as *public* +// whose parent type is not declared as *public*. +// +// In such situation *public* means, *can be accessed +// from anywhere my parent type is visible*. Some +// developers think this is an elegant language construct, +// some others find it misleading. +// +// This rule can be deactivated if you don't agree with it. +// Read the whole debate here: +// http://ericlippert.com/2014/09/15/internal-or-public/ +// +// By default issues of this rule have a **Low** severity +// because they reflect more an advice than a problem. +// + +// +// Declare the method as *internal* if it is used outside of +// its type, else declare it as *private*. +//]]> + Event handler methods should be declared as private or protected +// ND1808:EventHandlerMethodsShouldBeDeclaredAsPrivateOrProtected + +warnif count > 0 +from m in Application.Methods where + !(m.IsPrivate || m.IsProtected) && + !m.IsGeneratedByCompiler && + + // A method is considered as an event handler if… + m.NbParameters == 2 && // … it has two parameters … + m.Name.Contains("Object") && // … of types Object … + m.Name.Contains("EventArgs") && // … and EventArgs + + // Discard special cases + !m.ParentType.IsDelegate && + !m.IsGeneratedByCompiler + +select new { + m, + m.Visibility, + Debt = 2.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// Think of a event handler like for example *OnClickButton()*. +// Typically such method must be declared as *private* +// and shouldn't be called in other context than event firing. +// +// Such method can also be declared as *protected* because +// some designers such as the ASP.NET designer, generates such method +// as *protected* to let a chance to sub-classes to call it. +// + +// +// If you have the need that event handler method should be called +// from another class, then find a code structure that more +// closely matches the concept of what you're trying to do. +// Certainly you don't want the other class to click a button; you +// want the other class to do something that clicking a button +// also do. +//]]> + Wrong usage of CannotDecreaseVisibilityAttribute +// ND1809:WrongUsageOfCannotDecreaseVisibilityAttribute + +warnif count > 0 + +let tAttr = Types.WithFullName("NDepend.Attributes.CannotDecreaseVisibilityAttribute").FirstOrDefault() +where tAttr != null + +// Get types that do a wrong usage of CannotDecreaseVisibilityAttribute +let types = from t in Application.Types where + t.HasAttribute("NDepend.Attributes.CannotDecreaseVisibilityAttribute".AllowNoMatch()) && + ( t.Visibility == t.OptimalVisibility || + + // Static types that define only const fields cannot be seen as used in IL code. + // They don't need to be tagged with CannotDecreaseVisibilityAttribute. + (t.IsStatic && t.NbMethods == 0 && !t.Fields.Any(f => !f.IsLiteral)) + ) + select t + +// Get methods that do a wrong usage of CannotDecreaseVisibilityAttribute +let methods = from m in Application.Methods where + m.HasAttribute("NDepend.Attributes.CannotDecreaseVisibilityAttribute".AllowNoMatch()) && + m.Visibility == m.OptimalVisibility + select m + +// Get fields that do a wrong usage of CannotDecreaseVisibilityAttribute +let fields = from f in Application.Fields where + f.HasAttribute("NDepend.Attributes.CannotDecreaseVisibilityAttribute".AllowNoMatch()) && + f.Visibility == f.OptimalVisibility + select f + +from member in types.Cast().Concat(methods).Concat(fields) +select new { + member, + Debt = 30.ToSeconds().ToDebt(), + Severity = Severity.Low +} + +// +// The attribute **NDepend.Attributes.CannotDecreaseVisibilityAttribute** +// is defined in *NDepend.API.dll*. If you don't want to reference +// *NDepend.API.dll* you can declare it in your code. +// +// Usage of this attribute means that a code element visibility is not +// optimal (it can be lowered like for example from *public* to *internal*) +// but shouldn’t be modified anyway. Typical reasons to do so include: +// +// • Public code elements consumed through reflection, through a mocking +// framework, through XML or binary serialization, through designer, +// COM interfaces… +// +// • Non-private code element invoked by test code, that would be difficult +// to reach from test if it was declared as *private*. +// +// In such situation *CannotDecreaseVisibilityAttribute* is used to avoid +// that default rules about not-optimal visibility warn. Using this +// attribute can be seen as an extra burden, but it can also be seen as +// an opportunity to express in code: **Don't change the visibility else +// something will be broken** +// +// In this context, this code rule matches code elements +// (types, methods, fields) that are tagged with this attribute, +// but still already have an optimal visibility. +// + +// +// Just remove *CannotDecreaseVisibilityAttribute* tagging of +// types, methods and fields matched by this rule +// because this tag is not useful anymore. +// +// By default issues of this rule have a **Low** severity +// because they reflect more an advice than a problem. +//]]> + Methods that should be declared as 'public' in C#, 'Public' in VB.NET + +from m in Application.Methods where + m.ShouldBePublic +let usedInAssemblies = m.MethodsCallingMe.ParentAssemblies().Except(m.ParentAssembly) +select new { + m, + m.ParentAssembly, + usedInAssemblies, + m.MethodsCallingMe +} + +// +// This code query lists methods that *should* be declared +// as *public*. Such method is actually declared as *internal* +// and is consumed from outside its parent assembly +// thanks to the attribute +// *System.Runtime.CompilerServices.InternalsVisibleToAttribute*. +// +// This query relies on the property +// *NDepend.CodeModel.IMember.ShouldBePublic* +// https://www.ndepend.com/api/webframe.html#NDepend.API~NDepend.CodeModel.IMember~ShouldBePublic.html +// +// This is just a code query, it is not intended to advise +// you to declare the method as *public*, but to inform you +// that the code actually relies on the peculiar behavior +// of the attribute *InternalsVisibleToAttribute*. +//]]> + + + Fields should be marked as ReadOnly when possible +// ND1900:FieldsShouldBeMarkedAsReadOnlyWhenPossible + +warnif count > 0 +from f in JustMyCode.Fields where + f.IsImmutable && + !f.IsInitOnly && // The condition IsInitOnly matches fields that + // are marked with the C# readonly keyword + // (ReadOnly in VB.NET). + !f.IsGeneratedByCompiler && + !f.IsEventDelegateObject && + !f.ParentType.IsEnumeration && + !f.IsLiteral && + + // Don't warn if a method using the field is also calling a method that has 'ref' and 'out' parameters. + // This could lead to false positive. A field used in a 'ref' or 'out' parameter cannot be set as read-only. + f.MethodsUsingMe.SelectMany(m => m.MethodsCalled).FirstOrDefault(m => m.Name.Contains("&")) == null && + + // Don't warn for fields tagged with these Unity specific attribute + !f.HasAttribute("UnityEngine.SerializeField".AllowNoMatch()) && + !f.HasAttribute("UnityEngine.SerializeReference".AllowNoMatch()) + +select new { + f, + f.MethodsReadingMeButNotAssigningMe, + f.MethodsAssigningMe, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns about instance and static fields that +// can be declared as **readonly**. +// +// This source code of this rule is based on the conditon +// *IField.IsImmutable*. +// https://www.ndepend.com/api/webframe.html#NDepend.API~NDepend.CodeModel.IField~IsImmutable.html +// +// A field that matches the condition *IsImmutable* +// is a field that is assigned only by constructors +// of its class. +// +// For an *instance field*, this means its value +// will remain constant through the lifetime +// of the object. +// +// For a *static field*, this means its value will +// remain constant through the lifetime of the +// program. +// + +// +// Declare the field with the C# *readonly* keyword +// (*ReadOnly* in VB.NET). This way the intention +// that the field value shouldn't change is made +// explicit. +//]]> + Avoid non-readonly static fields +// ND1901:AvoidNonReadOnlyStaticFields + +warnif count > 0 +from f in Application.Fields +where f.IsStatic && + !f.IsEnumValue && + !f.IsGeneratedByCompiler && + !f.IsLiteral && + !f.IsInitOnly + +let methodAssigningField = f.MethodsAssigningMe + +select new { + f, + methodAssigningField, + Debt = (2+8*methodAssigningField.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule warns about static fields that are not +// declared as read-only. +// +// In *Object-Oriented-Programming* the natural artifact +// to hold states that can be modified is **instance fields**. +// Such mutable static fields create *confusion* about +// the expected state at runtime and impairs the code +// testability since the same mutable state is re-used for +// each test. +// +// More discussion on the topic can be found here: +// https://blog.ndepend.com/the-proper-usages-of-the-keyword-static-in-c/ +// + +// +// If the *static* field is just assigned once in the program +// lifetime, make sure to declare it as *readonly* and assign +// it inline, or from the static constructor. +// +// Else if methods other than the static constructor need to +// assign the state hold by the static field, refactoring must +// occur to ensure that this state is hold through an instance +// field. +//]]> + Avoid static fields with a mutable field type +// ND1902:AvoidStaticFieldsWithAMutableFieldType + +warnif count > 0 +from f in Application.Fields +where f.IsStatic && + !f.IsEnumValue && + !f.IsGeneratedByCompiler && + !f.IsLiteral + +let fieldType = f.FieldType +where fieldType != null && + !fieldType.IsThirdParty && + !fieldType.IsInterface && + !fieldType.IsImmutable + +select new { + f, + mutableFieldType = fieldType , + isFieldImmutable = f.IsImmutable ? "Immutable" : "Mutable", + isFieldReadOnly = f.IsInitOnly ? "ReadOnly" : "Not ReadOnly", + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns about static fields whose field type +// is mutable. In such case the static field is +// holding a state that can be modified. +// +// In *Object-Oriented-Programming* the natural artifact +// to hold states that can be modified is **instance fields**. +// Hence such static fields create *confusion* about +// the expected state at runtime. +// +// More discussion on the topic can be found here: +// https://blog.ndepend.com/the-proper-usages-of-the-keyword-static-in-c/ +// + +// +// To fix violations of this rule, make sure to +// hold mutable states through objects that are passed +// **explicitly** everywhere they need to be consumed, in +// opposition to mutable object hold by a static field that +// makes it modifiable from a bit everywhere in the program. +//]]> + Structures should be immutable +// ND1903:StructuresShouldBeImmutable + +warnif count > 0 from t in JustMyCode.Types where + t.IsStructure && + !t.IsImmutable + +let mutableFields = t.Fields.Where(f => !f.IsImmutable) + +select new { + t, + t.NbLinesOfCode, + mutableFields, + Debt = (3+2*mutableFields.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// An object is immutable if its state doesn’t change once the +// object has been created. Consequently, a structure or a class +// is immutable if its instances fields are only assigned inline +// or from constructor(s). +// +// But for structure it is a bit different. **Structures are value +// types** which means instances of structures are copied when they are +// passed around (like through a method argument). +// +// So if you change a copy you are changing only that copy, not the +// original and not any other copies which might be around. Such +// situation is very different than what happen with instances of +// classes. Hence developers are not used to work with modified values +// and doing so introduces *confusion* and is *error-prone*. +// + +// +// Make sure matched structures are immutable. This way, all +// automatic copies of an original instance, resulting from being +// *passed by value* will hold the same values and there will be +// no surprises. +// +// Note that since C#7.2 you can prefix a structure declaration +// with the keyword readonly to enforce that it is an immutable +// structure. +// +// If your structure is immutable then if you want to change +// a value, you have to consciously do it by creating a new instance +// of the structure with the modified data. +//]]> + Property Getters should be immutable +// ND1904:PropertyGettersShouldBeImmutable + +warnif count > 0 from m in Application.Methods where + !m.IsGeneratedByCompiler && + m.IsPropertyGetter && + ( ( !m.IsStatic && m.ChangesObjectState) || + ( m.IsStatic && m.ChangesTypeState) ) + +let propertyName = m.SimpleName.Substring(4,m.SimpleName.Length-4) +let setterSimpleName = "set_" + propertyName + + +let fieldsAssigned = m.FieldsAssigned.Where(f => + + // Don't count field that have a name similar to the property name + // to avoid matching lazy initialization situations. + !(propertyName.Length >= 4 && + f.SimpleName.Length >= 4 && + // Don't count the first 3 characters of the field name, + // to avoid special field name formatting like 'm_X' or '_x' + propertyName.EndsWith(f.SimpleName.Substring(3, f.SimpleName.Length -3))) + && + + // Don't count field that are assigned only by the property getter and the related property setter. + f.MethodsAssigningMe.Any(m1 => m1 != m && + !(m1.IsPropertySetter && m1.SimpleName == setterSimpleName))) +where fieldsAssigned.Any() +let otherMethodsAssigningSameFields = fieldsAssigned.SelectMany(f => f.MethodsAssigningMe.Where(m1 => m1 != m)) + +select new { + m, + m.NbLinesOfCode, + fieldsAssigned, + otherMethodsAssigningSameFields, + Debt = (2 + 5*fieldsAssigned.Count() + 5*otherMethodsAssigningSameFields.Count() ).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// It is not expected that a state gets modified when +// accessing a property getter. Hence doing so create +// confusion and property getters should be pure methods, +// they shouldn't assign any field. +// +// This rule doesn't match property getters that assign a field +// not assigned by any other methods than the getter itself +// and the corresponding property setter. Hence this rule avoids +// matching *lazy initialization at first access* of a state. +// In such situation the getter assigns a field at first access +// and from the client point of view, lazy initialization +// is an invisible implementation detail. +// +// A field assigned by a property with a name similar to the +// property name are not count either, also to avoid matching +// *lazy initialization at first access* situations. +// + +// +// Make sure that matched property getters don't assign any +// field. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 2 minutes plus 5 minutes per field assigned and +// 5 minutes per other method assigning such field. +//]]> + A field must not be assigned from outside its parent hierarchy types +// ND1905:AFieldMustNotBeAssignedFromOutsideItsParentHierarchyTypes + +warnif count > 0 +from f in JustMyCode.Fields.Where(f => + (f.IsInternal || f.IsPublic) && + !f.IsGeneratedByCompiler && + !f.IsImmutable && + !f.IsEnumValue) + +let methodsAssignerOutsideOfMyType = f.MethodsAssigningMe.Where( + m =>!m.IsGeneratedByCompiler && + m.ParentType != f.ParentType && + !m.ParentType.DeriveFrom(f.ParentType) ) + +where methodsAssignerOutsideOfMyType.Any() + +select new { + f, + methodsAssignerOutsideOfMyType, + Debt = (5*methodsAssignerOutsideOfMyType.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is related to the rule *Fields should be declared as +// private*. It matches any **public or internal, mutable field** +// that is assigned from outside its parent class and subclasses. +// +// Fields should be considered as **implementation details** +// and as a consequence they should be declared as private. +// +// If something goes wrong with a *non-private field*, +// the culprit can be anywhere, and so in order to track down +// the bug, you may have to look at quite a lot of code. +// +// A private field, by contrast, can only be assigned from +// inside the same class, so if something goes wrong with that, +// there is usually only one source file to look at. +// + +// +// Matched fields must be declared as *protected* and even better +// as *private*. +// +// Alternatively, if the field can reference immutable states, +// it can remain visible from the outside, but then must be +// declared as *readonly*. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 5 minutes per method outside the parent hierarchy +// that assigns the matched field. +//]]> + Don't assign a field from many methods +// ND1906:DontAssignAFieldFromManyMethods + +warnif count > 0 +from f in JustMyCode.Fields where + !f.IsEnumValue && + !f.IsImmutable && + !f.IsInitOnly && + !f.IsGeneratedByCompiler && + !f.IsEventDelegateObject + +let methodsAssigningMe = f.MethodsAssigningMe.Where(m => !m.IsConstructor) + +// The threshold 4 is arbitrary and it should avoid matching too many fields. +// Threshold is even lower for static fields because this reveals situations even more complex. +where methodsAssigningMe.Count() >= (!f.IsStatic ? 4 : 2) + +select new { + f, + methodsAssigningMe, + f.MethodsReadingMeButNotAssigningMe, + f.MethodsUsingMe, + Debt = (4+(f.IsStatic ? 10 : 5)).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// A field assigned from many methods is a symptom of **bug-prone code**. +// Notice that: +// +// • For an instance field, constructor(s) of its class that assign the field are not counted. +// +// • For a static field, the class constructor that assigns the field is not counted. +// +// The default threshold for *instance fields* is equal to *4 or more than 4 methods +// assigning the instance field*. Such situation makes harder to anticipate the +// field state at runtime. The code is then complicated to read, hard to debug +// and hard to maintain. Hard-to-solve bugs due to corrupted state are often the +// consequence of fields *anarchically assigned*. +// +// The situation is even more complicated if the field is *static*. +// Indeed, such situation potentially involves global random accesses from +// various parts of the application. This is why this rule provides a lower +// threshold equals to *2 or more than 2 methods assigning the static field*. +// +// If the object containing such field is meant to be used from multiple threads, +// there are **alarming chances** that the code is unmaintainable and bugged. +// When multiple threads are involved, the rule of thumb is to use immutable objects. +// +// If the field type is a reference type (interfaces, classes, strings, delegates) +// corrupted state might result in a *NullReferenceException*. +// If the field type is a value type (number, boolean, structure) +// corrupted state might result in wrong result not even signaled by an exception +// thrown. +// + +// +// There is no straight advice to refactor the number of methods responsible +// for assigning a field. Sometime the situation is simple enough, like when +// a field that hold an indentation state is assigned by many writer methods. +// Such situation only requires to define two methods *IndentPlus()/IndentMinus()* +// that assign the field, called from all writers methods. +// +// Sometime the solution involves rethinking and then rewriting +// a complex algorithm. Such field can sometime become just a variable accessed +// locally by a method or a *closure*. Sometime, just rethinking the life-time +// and the role of the parent object allows the field to become immutable +// (i.e assigned only by the constructor). +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 4 minutes plus 5 minutes per method assigning the instance field +// or 10 minutes per method assigning the static field. +//]]> + Do not declare read only mutable reference types +// ND1907:DoNotDeclareReadOnlyMutableReferenceTypes + +warnif count > 0 +from f in JustMyCode.Fields where + f.IsInitOnly && + !f.ParentType.IsPrivate && + !f.IsPrivate && + f.FieldType != null && + f.FieldType.IsClass && + !f.FieldType.IsThirdParty && + !f.FieldType.IsImmutable +select new { + f, + f.FieldType, + FieldVisibility = f.Visibility, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// This rule is violated when a *public* or *internal* +// type contains a *public* or *internal* read-only field +// whose field type is a mutable reference type. +// +// This situation gives the false impression that the +// value can't change, when actually it's only the field +// value that can't change, but the object state +// can still change. +// + +// +// To fix a violation of this rule, +// replace the field type with an immutable type, +// or declare the field as *private*. +// +// By default issues of this rule have a **Low** severity +// because they reflect more an advice than a problem. +//]]> + Array fields should not be read only +// ND1908:ArrayFieldsShouldNotBeReadOnly + +warnif count > 0 +from f in Application.Fields where + f.IsInitOnly && + f.IsPubliclyVisible && + f.FieldType != null && + f.FieldType.FullName == "System.Array" +select new { + f, + FieldVisibility = f.Visibility, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// This rule is violated when a publicly visible field +// that holds an array, is declared read-only. +// +// This situation represents a *security vulnerability*. +// Because the field is read-only it cannot be changed to refer +// to a different array. However, the elements of the array +// that are stored in a read-only field can be changed. +// Code that makes decisions or performs operations that are +// based on the elements of a read-only array that can be publicly +// accessed might contain an exploitable security vulnerability. +// + +// +// To fix the security vulnerability that is identified by +// this rule do not rely on the contents of a read-only array +// that can be publicly accessed. It is strongly recommended +// that you use one of the following procedures: +// +// • Replace the array with a strongly typed collection +// that cannot be changed. See for example: +// *System.Collections.Generic.IReadOnlyList* ; +// *System.Collections.Generic.IReadOnlyCollection* ; +// *System.Collections.ReadOnlyCollectionBase* +// +// • Or replace the public field with a method that returns a clone +// of a private array. Because your code does not rely on +// the clone, there is no danger if the elements are modified. +// +// By default issues of this rule have a **Low** severity +// because they reflect more an advice than a problem. +//]]> + Types tagged with ImmutableAttribute must be immutable +// ND1909:TypesTaggedWithImmutableAttributeMustBeImmutable + +warnif count > 0 +from t in Application.Types where + t.HasAttribute ("NDepend.Attributes.ImmutableAttribute".AllowNoMatch()) && + !t.IsImmutable +let culpritFields = t.Fields.Where(f => !f.IsStatic && !f.IsImmutable) +select new { + t, + culpritFields, + Debt = (5+10*culpritFields.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// An object is immutable if its state doesn’t change once the +// object has been created. Consequently, a structure or a class +// is immutable if its instances fields are only assigned inline +// or from constructor(s). +// +// An attribute **NDepend.Attributes.ImmutableAttribute** can be +// used to express in code that a type is immutable. In such +// situation, the present code rule checks continuously that the +// type remains immutable whatever the modification done. +// +// This rule warns when a type that is tagged with +// *ImmutableAttribute* is actually not immutable anymore. +// +// Notice that *FullCoveredAttribute* is defined in *NDepend.API.dll* +// and if you don't want to link this assembly, you can create your +// own *FullCoveredAttribute* and adapt the rule. +// + +// +// First understand which modification broke the type immutability. +// The list of *culpritFields* provided in this rule result can help. +// Then try to refactor the type to bring it back to immutability. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 5 minutes plus 10 minutes per culprit field. +//]]> + Types immutable should be tagged with ImmutableAttribute +// ND1910:TypesImmutableShouldBeTaggedWithImmutableAttribute + +// warnif count > 0 <-- not a code rule per default + +from t in Application.Types where + !t.HasAttribute ("NDepend.Attributes.ImmutableAttribute".AllowNoMatch()) && + t.IsImmutable && + // Don't need to tag record classes with [Immutable] + !t.IsRecord +select new { + t, + t.NbLinesOfCode, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// An object is immutable if its state doesn’t change once the +// object has been created. Consequently, a structure or a class +// is immutable if its instances fields are only assigned inline +// or from constructor(s). +// +// This code query lists immutable type that are not tagged with +// an **ImmutableAttribute**. By using such attribute, you can express +// in source code the intention that a class is immutable, and +// should remain immutable in the future. Benefits of using +// an **ImmutableAttribute** are twofold: +// +// • Not only the intention is expressed in source code, +// +// • but it is also continuously checked by the rule +// *Types tagged with ImmutableAttribute must be immutable*. +// + +// +// Just tag types matched by this code query with **ImmutableAttribute** +// that can be found in *NDepend.API.dll*, +// or by an attribute of yours defined in your own code +// (in which case this code query must be adapted). +//]]> + Methods tagged with PureAttribute must be pure +// ND1911:MethodsTaggedWithPureAttributeMustBePure + +warnif count > 0 +from m in Application.Methods where + ( m.HasAttribute ("NDepend.Attributes.PureAttribute".AllowNoMatch()) || + m.HasAttribute ("System.Diagnostics.Contract.PureAttribute".AllowNoMatch()) ) && + ( m.ChangesObjectState || m.ChangesTypeState ) && + m.NbLinesOfCode > 0 + +let fieldsAssigned = m.FieldsAssigned + +select new { + m, + m.NbLinesOfCode, + fieldsAssigned, + Debt = 15.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// A method is pure if its execution doesn’t change +// the value of any instance or static field. +// A pure method is just a **function** that output +// a result from inputs. +// Pure methods naturally simplify code by **limiting +// side-effects**. +// +// An attribute **PureAttribute** can be +// used to express in code that a method is pure. In such +// situation, the present code rule checks continuously that the +// method remains pure whatever the modification done. +// +// This rule warns when a method that is tagged with +// *PureAttribute* is actually not pure anymore. +// +// Notice that *NDepend.Attributes.PureAttribute* is defined +// in *NDepend.API.dll* and if you don't want to link this +// assembly, you can also use +// *System.Diagnostics.Contract.PureAttribute* +// or create your own *PureAttribute* and adapt the rule. +// +// Notice that *System.Diagnostics.Contract.PureAttribute* is +// taken account by the compiler only when the VS project has +// Microsoft Code Contract enabled. +// + +// +// First understand which modification broke the method purity. +// Then refactor the method to bring it back to purity. +//]]> + Pure methods should be tagged with PureAttribute +// ND1912:PureMethodsShouldBeTaggedWithPureAttribute + +// warnif count > 0 <-- not a code rule per default + +from m in Application.Methods where + !m.IsGeneratedByCompiler && + !m.HasAttribute ("NDepend.Attributes.PureAttribute".AllowNoMatch()) && + !m.HasAttribute ("System.Diagnostics.Contract.PureAttribute".AllowNoMatch()) && + !m.ChangesObjectState && !m.ChangesTypeState && + m.NbLinesOfCode > 0 +select new { + m, + m.NbLinesOfCode, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// A method is pure if its execution doesn’t change +// the value of any instance or static field. +// Pure methods naturally simplify code by **limiting +// side-effects**. +// +// This code query lists pure methods that are not tagged with +// a **PureAttribute**. By using such attribute, you can express +// in source code the intention that a method is pure, and +// should remain pure in the future. Benefits of using +// a **PureAttribute** are twofold: +// +// • Not only the intention is expressed in source code, +// +// • but it is also continuously checked by the rule +// *Methods tagged with PureAttribute must be pure*. +// +// This code query is not by default defined as a code rule +// because certainly many of the methods of the code base +// are matched. Hence fixing all matches and then +// maintaining the rule unviolated might require a lot of +// work. This may *counter-balance* such rule benefits. +// + +// +// Just tag methods matched by this code query with +// *NDepend.Attributes.PureAttribute* +// that can be found in *NDepend.API.dll*, +// or with *System.Diagnostics.Contract.PureAttribute*, +// or with an attribute of yours defined in your own code +// (in which case this code query must be adapted). +// +// Notice that *System.Diagnostics.Contract.PureAttribute* is +// taken account by the compiler only when the VS project has +// Microsoft Code Contract enabled. +//]]> + Record classes should be immutable +// ND1913:RecordClassesShouldBeImmutable + +warnif count > 0 + +from t in Application.Types +where t.IsRecord && !t.IsImmutable +let culpritSetters = t.InstanceMethods.Where( + m => m.IsPropertySetter && !m.IsPropertyInit) + +let methodsCallingCulpritSetters = culpritSetters.SelectMany( + m => m.MethodsCallingMe.Where(mc => mc.ParentType != t) ) + +select new { t, + culpritSetters, + methodsCallingCulpritSetters , + Debt = (8 + 3*methodsCallingCulpritSetters.Count() + + 3*methodsCallingCulpritSetters.Count()) + .ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// The language intention for C#9 **record** is to make it easy +// to create classes with **value-based semantic**. +// Value-based semantic for a class means +// 1) **Value-based equality** and 2) **Immutability**. +// These are explained in this article: +// https://blog.ndepend.com/c9-records-immutable-classes/ +// +// However it is still possible to create a mutable state in a +// record with a property setter that is not declared as *init*. +// +// It is recommended to avoid having mutable states in +// a record class because this breaks the **value-based semantic**. +// For example changing the state of a record object used as a key +// in a hash table can corrupt the hash table integrity. +// +// Also changing the state of a record object can lead to +// unexpected value-based equality results. In both cases such code-smell +// ends up provoking bugs that are difficult to spot and difficult to fix. +// + +// +// To fix an issue of this rule you must make sure that the matched record +// becomes immutable. To do so mutable property setters +// (*culpritSetters* in the result) of the setter must be transformed in +// property initializers with the *init* C# keyword. +// Callers of the mutable property setters (*methodsCallingCulpritSetters * +// in the result) must be also refactored to avoid changing the record states. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 8 minutes plus 3 minutes per mutable property setter +// and per method calling such mutable property setter. +//]]> + + + Instance fields naming convention +// ND2000:InstanceFieldsNamingConvention + +warnif count > 0 +let isUnityProject = ThirdParty.Assemblies.Any(a => a.Name.StartsWith("UnityEngine")) +from f in JustMyCode.Fields where + !f.IsStatic && + !f.IsLiteral && + !f.IsGeneratedByCompiler && + !f.IsSpecialName && + !f.IsEventDelegateObject && + !( + // Instance field name starting with a lower-case letter + (f.Name.Length >= 1 && char.IsLetter(f.Name[0]) && char.IsLower(f.Name[0])) || + + // Instance field name starting with "_" followed with a lower-case letter + (f.Name.Length >= 2 && f.Name.StartsWith("_") && char.IsLetter(f.Name[1]) && char.IsLower(f.Name[1])) || + + // Instance field name starting with "m_" followed with an upper-case letter + (f.Name.Length >= 3 && f.Name.StartsWith("m_") && char.IsLetter(f.Name[2]) && char.IsUpper(f.Name[2])) || + + // In Unity projects often there is no properties for high-performance reasons and fields are named like properties. + (isUnityProject && char.IsLetter(f.Name[0]) && char.IsUpper(f.Name[0])) + + ) && + // Don't check naming convention on serializable fields + !f.HasAttribute("System.Xml.Serialization.XmlAttributeAttribute".AllowNoMatch()) && + !f.HasAttribute("System.Runtime.Serialization.DataMemberAttribute".AllowNoMatch()) && + + // Don't warn if a method using the field is also calling a method that has 'ref' and 'out' parameters. + // This could lead to false positive. A field used in a 'ref' or 'out' parameter cannot be set as read-only. + f.MethodsUsingMe.SelectMany(m => m.MethodsCalled).FirstOrDefault(m => m.Name.Contains("&")) == null + +select new { + f, + f.SizeOfInst, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// By default the presents rule supports the 3 most used naming +// conventions for instance fields: +// +// • Instance field name starting with a lower-case letter +// +// • Instance field name starting with "_" followed with a lower-case letter +// +// • Instance field name starting with "m_" followed with an upper-case letter +// +// The rule can be easily adapted to your own company naming convention. +// +// In terms of behavior, a *static field* is something completely different +// than an *instance field*, so it is interesting to differentiate them at +// a glance through a naming convetion. +// +// This is why it is advised to use a specific naming convention for instance +// field like name that starts with **m_**. +// +// Related discussion: +// http://codebetter.com/patricksmacchia/2013/09/04/on-hungarian-notation-for-instance-vs-static-fields-naming/ +// + +// +// Once the rule has been adapted to your own naming convention +// make sure to name all matched instance fields adequately. +//]]> + Static fields naming convention +// ND2001:StaticFieldsNamingConvention + +warnif count > 0 +from f in JustMyCode.Fields where + f.IsStatic && + !f.IsLiteral && + !f.IsGeneratedByCompiler && + !f.IsSpecialName && + !f.IsEventDelegateObject && + !( + // Static field name starting with an upper-case letter + (f.Name.Length >= 1 && char.IsLetter(f.Name[0]) && char.IsUpper(f.Name[0])) || + + // Static field name starting with "_" followed with an upper-case letter + (f.Name.Length >= 2 && f.Name.StartsWith("_") && char.IsLetter(f.Name[1]) && char.IsUpper(f.Name[1])) || + + // Static field name starting with "s_" followed with an upper-case letter + (f.Name.Length >= 3 && f.Name.StartsWith("s_") && char.IsLetter(f.Name[2]) && char.IsUpper(f.Name[2])) + ) +select new { + f, + f.SizeOfInst, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// By default the presents rule supports the 3 most used naming +// conventions for static fields: +// +// • Static field name starting with an upper-case letter +// +// • Static field name starting with "_" followed with an upper-case letter +// +// • Static field name starting with "s_" followed with an upper-case letter +// +// The rule can be easily adapted to your own company naming convention. +// +// In terms of behavior, a *static field* is something completely different +// than an *instance field*, so it is interesting to differentiate them at +// a glance through a naming convetion. +// +// This is why it is advised to use a specific naming convention for static +// field like name that starts with **s_**. +// +// Related discussion: +// http://codebetter.com/patricksmacchia/2013/09/04/on-hungarian-notation-for-instance-vs-static-fields-naming/ +// + +// +// Once the rule has been adapted to your own naming convention +// make sure to name all matched static fields adequately. +//]]> + Interface name should begin with a 'I' +// ND2002:InterfaceNameShouldBeginWithAI + +// Don't query all Application.Types because some interface generated might not start with an 'I' +// like for example when adding a WSDL reference to a project. +warnif count > 0 from t in JustMyCode.Types where + t.IsInterface && + // Don't apply this rule for COM interfaces. + !t.HasAttribute("System.Runtime.InteropServices.ComVisibleAttribute".AllowNoMatch()) + +// Discard outter type(s) name prefix for nested types +let name = !t.IsNested ? + t.Name : + t.Name.Substring(t.Name.LastIndexOf('+') + 1, t.Name.Length - t.Name.LastIndexOf('+') - 1) + +where name[0] != 'I' +select new { + t, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// In the .NET world, interfaces names are commonly prefixed +// with an upper case **I**. This rule warns about interfaces +// whose names don't follow this convention. Because this +// naming convention is widely used and accepted, we +// recommend abiding by this rule. +// +// Typically COM interfaces names don't follow this rule. +// Hence this code rule doesn't take care of interfaces tagged +// with *ComVisibleAttribute*. +// + +// +// Make sure that matched interfaces names are prefixed with +// an upper **I**. +//]]> + Abstract base class should be suffixed with 'Base' +// ND2003:AbstractBaseClassShouldBeSuffixedWithBase + +warnif count > 0 from t in Application.Types where + t.IsAbstract && + t.IsClass && + + t.BaseClass != null && + t.BaseClass.FullName == "System.Object" && + + ((!t.IsGeneric && !t.NameLike (@"Base$")) || + ( t.IsGeneric && !t.NameLike (@"Base<"))) +select new { + t, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns about *abstract classes* whose names are not +// suffixed with **Base**. It is a common practice in the .NET +// world to suffix base classes names with **Base**. +// +// Notice that this rule doesn't match abstract classes that +// are in a middle of a hierarchy chain. +// In other words, only base classes that derive directly +// from *System.Object* are matched. +// + +// +// Suffix the names of matched base classes with **Base**. +//]]> + Exception class name should be suffixed with 'Exception' +// ND2004:ExceptionClassNameShouldBeSuffixedWithException + +warnif count > 0 from t in Application.Types where + t.IsExceptionClass && + + // We use SimpleName, because in case of generic Exception type + // SimpleName suppresses the generic suffix (like ). + !t.SimpleNameLike(@"Exception$") && + !t.SimpleNameLike(@"ExceptionBase$") // Allow the second suffix Base + // for base exception classes. +select new { + t, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule warns about *exception classes* whose names are not +// suffixed with **Exception**. It is a common practice in the .NET +// world to suffix exception classes names with **Exception**. +// +// For exception base classes, the suffix **ExceptionBase** +// is also accepted. +// + +// +// Suffix the names of matched exception classes with **Exception**. +//]]> + Attribute class name should be suffixed with 'Attribute' +// ND2005:AttributeClassNameShouldBeSuffixedWithAttribute + +warnif count > 0 from t in Application.Types where + t.IsAttributeClass && + !t.NameLike (@"Attribute$") +select new { + t, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule warns about *attribute classes* whose names are not +// suffixed with **Attribute**. It is a common practice in the .NET +// world to suffix attribute classes names with **Attribute**. +// + +// +// Suffix the names of matched attribute classes with **Attribute**. +//]]> + Types name should begin with an Upper character +// ND2006:TypesNameShouldBeginWithAnUpperCharacter + +warnif count > 0 +let isAspNetApp = ThirdParty.Assemblies.WithName("System.Web").Any() +from t in JustMyCode.Types where + // The name of a type should begin with an Upper letter. + !t.SimpleNameLike (@"^[A-Z]") && + + // Except if it is generated by compiler. + !t.IsSpecialName && + !t.IsGeneratedByCompiler && + + // Special default ASP.NET type named "_Default" + !(isAspNetApp && t.SimpleName == "_Default") + +select new { + t, + // We show the type simple name + // that doesn't include the parent type name + // for nested types. + t.SimpleName, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns about *types* whose names don't start +// with an Upper character. It is a common practice in the .NET +// world to use **Pascal Casing Style** to name types. +// +// **Pascal Casing Style** : The first letter in the identifier +// and the first letter of each subsequent concatenated word +// are capitalized. For example: *BackColor* +// + +// +// *Pascal Case* the names of matched types. +//]]> + Methods name should begin with an Upper character +// ND2007:MethodsNameShouldBeginWithAnUpperCharacter + +warnif count > 0 +from m in JustMyCode.Methods where + !m.NameLike (@"^[A-Z]") && + !m.IsSpecialName && + !m.IsGeneratedByCompiler && + !m.IsExplicitInterfaceImpl // This can generate unexpected method name. +select new { + m, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns about *methods* whose names don't start +// with an Upper character. It is a common practice in the .NET +// world to use **Pascal Casing Style** to name methods. +// +// **Pascal Casing Style** : The first letter in the identifier +// and the first letter of each subsequent concatenated word +// are capitalized. For example: *ComputeSize* +// + +// +// *Pascal Case* the names of matched methods. +//]]> + Do not name enum values 'Reserved' +// ND2008:DoNotNameEnumValuesReserved + +warnif count > 0 +from f in Application.Fields where + f.IsEnumValue && + f.NameLike (@"Reserved") +select new { + f, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule assumes that an enumeration member +// with a name that contains **"Reserved"** +// is not currently used but is a placeholder to +// be renamed or removed in a future version. +// Renaming or removing a member is a breaking +// change. You should not expect users to ignore +// a member just because its name contains +// **"Reserved"** nor can you rely on users to read or +// abide by documentation. Furthermore, because +// reserved members appear in object browsers +// and smart integrated development environments, +// they can cause confusion as to which members +// are actually being used. +// +// Instead of using a reserved member, add a +// new member to the enumeration in the future +// version. +// +// In most cases, the addition of the new +// member is not a breaking change, as long as the +// addition does not cause the values of the +// original members to change. +// + +// +// To fix a violation of this rule, remove or +// rename the member. +//]]> + Avoid types with name too long +// ND2009:AvoidTypesWithNameTooLong + +warnif count > 0 +from t in JustMyCode.Types +where !t.IsGeneratedByCompiler + +where t.SimpleName.Length > 40 +orderby t.SimpleName.Length descending +select new { + t, + t.SimpleName, + NameLength = t.SimpleName.Length, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// Types with a name too long tend to decrease code readability. +// This might also be an indication that a type is doing too much. +// +// This rule matches types with names with more than 40 characters. +// + +// +// To fix a violation of this rule, rename the type with a shortest name +// or eventually split the type in several more fine-grained types. +//]]> + Avoid methods with name too long +// ND2010:AvoidMethodsWithNameTooLong + +warnif count > 0 + +// First get test method for which we allow long names +let testAttr = ThirdParty.Types.WithNameIn("FactAttribute", "TestAttribute", "TestCaseAttribute") +let testMethods = Methods.TaggedWithAnyAttributes(testAttr).ToHashSetEx() + +from m in JustMyCode.Methods where + + // Explicit Interface Implementation methods are + // discarded because their names are prefixed + // with the interface name. + !m.IsExplicitInterfaceImpl && + !m.IsGeneratedByCompiler && + ((!m.IsSpecialName && m.SimpleName.Length > 40) || + // Property getter/setter are prefixed with "get_" "set_" of length 4. + ( m.IsSpecialName && m.SimpleName.Length - 4 > 40)) && + + // Don't match test methods + !testMethods.Contains(m) && + !m.SimpleName.Contains("Test") && + !m.ParentType.SimpleName.Contains("Test") && + !m.ParentNamespace.Name.Contains("Test") + +orderby m.SimpleName.Length descending + +select new { + m, + m.SimpleName, + NameLength = m.SimpleName.Length - (m.IsSpecialName ? 4 : 0), + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// Methods with a name too long tend to decrease code readability. +// This might also be an indication that a method is doing too much. +// +// This rule matches methods with names with more than 40 characters. +// +// However it is considered as a good practice to name unit tests +// in such a way with a very expressive name, hence this rule doens't match +// methods tagged with *FactAttribute*, *TestAttribute* and *TestCaseAttribute*. +// + +// +// To fix a violation of this rule, rename the method with a shortest name +// that equally conveys the behavior of the method. +// Or eventually split the method into several smaller methods. +//]]> + Avoid fields with name too long +// ND2011:AvoidFieldsWithNameTooLong + +warnif count > 0 from f in JustMyCode.Fields where + !f.IsGeneratedByCompiler && + f.Name.Length > 40 +orderby f.Name descending +select new { + f, + NameLength = f.Name.Length, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// Fields with a name too long tend to decrease code readability. +// +// This rule matches fields with names with more than 40 characters. +// + +// +// To fix a violation of this rule, rename the field with a shortest name +// that equally conveys the same information. +//]]> + Avoid having different types with same name +// ND2012:AvoidHavingDifferentTypesWithSameName + +warnif count > 0 + +// Special type names for which multiple types with such name are allowed. +let nonReportedTypeName = new [] { + "Program", "NamespaceDoc", "Initial", "Startup", "SwaggerConfig", "Constants", + "App", "Index", "Create", "Read", "Update", "Delete", "Edit", "Details", "View" } + +// Special type name suffixes for which multiple types with such suffix are allowed. +let nonReportedTypeNameSuffix = new [] { + "Service", "Model", "Controller", "Page", "Pages" } + +// This rule matches also collisions between +// application and third-party types sharing a same name. +let groups = JustMyCode.Types.Union(ThirdParty.Types) + // Discard nested types, whose name is + // prefixed with the parent type name. + .Where(t => !t.IsNested && + !nonReportedTypeName.Contains(t.Name) && + !t.Name.EndsWithAny(nonReportedTypeNameSuffix)) + + // Group types by name. + .GroupBy(t => t.Name) + +from @group in groups + where @group.Count() > 1 + + // Let's see if types with the same name are declared + // in different namespaces. + // (t.FullName is {namespaceName}.{typeName} ) + let groupsFullName = @group.GroupBy(t => t.FullName) + where groupsFullName.Count() > 1 + + // If several types with same name are declared in different namespaces + // eliminate the case where all types are declared in third-party assemblies. + let types= groupsFullName.SelectMany(g => g) + where types.Any(t => !t.IsThirdParty) + // Uncomment this line, to only gets naming collision involving + // both application and third-party types. + // && types.Any(t => t.IsThirdParty) + +orderby types.Count() descending + +select new { + // Order types by parent namespace and assembly name,this way we always get the same type across sessions. + // Without this astute, the same issue would be seen as added/removed when the first type choosen in the group + // was not always the same, a situation that actually happens. + // Also make sure that the chosen type is not a third-party one. + t = types.OrderBy(t => (t.IsThirdParty ? "1" : "0") + t.ParentNamespace.Name + t.ParentAssembly.Name).First(), + + // In the 'types' column, make sure to group matched types + // by parent assemblies and parent namespaces, to get a result + // more readable. + types, + + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule warns about multiple types with same name, +// that are defined in different *application* or +// *third-party* namespaces or assemblies. +// +// Such practice create confusion and also naming collision +// in source files that use different types with same name. +// + +// +// To fix a violation of this rule, rename concerned types. +//]]> + Avoid prefixing type name with parent namespace name +// ND2013:AvoidPrefixingTypeNameWithParentNamespaceName + +warnif count > 0 + +from n in JustMyCode.Namespaces +where n.Name.Length > 0 + +from t in n.ChildTypes +where + JustMyCode.Contains(t) && // Don't warn about generated code + !t.IsGeneratedByCompiler && + !t.IsNested && + t.Name.IndexOf(n.SimpleName) == 0 && + + // The type name is equal to namespace name or the type name contains another + // word that starts with an upper-case letter after the namespace name. + // This way we avoid matching false-positive where namespace name is "Stat" and type name is "Statistic". + (t.Name.Length == n.SimpleName.Length || char.IsUpper(t.Name[n.SimpleName.Length])) +select new { + t, + namespaceName = n.SimpleName, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns about situations where the parent namespace name +// is used as the prefix of a contained type. +// +// For example a type named "RuntimeEnvironment" +// declared in a namespace named "Foo.Runtime" +// should be named "Environment". +// +// Such situation creates naming redundancy with no readability gain. +// + +// +// To fix a violation of this rule, remove the prefix from the type name. +//]]> + Avoid naming types and namespaces with the same identifier +// ND2014:AvoidNamingTypesAndNamespacesWithTheSameIdentifier + +warnif count > 0 +let hashsetShortNames = Namespaces.Where(n => n.Name.Length > 0).Select(n => n.SimpleName).ToHashSetEx() + +from t in JustMyCode.Types +where hashsetShortNames.Contains(t.Name) +select new { + t, + namespaces = Namespaces.Where(n => n.SimpleName == t.Name), + Debt = 12.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule warns when a type and a namespace have the same name. +// +// For example when a type is named *Environment* +// and a namespace is named *Foo.Environment*. +// +// Such situation provokes tedious compiler resolution collision, +// and makes the code less readable because concepts are not +// concisely identified. +// + +// +// To fix a violation of this rule, renamed the concerned type or namespace. +//]]> + Don't call your method Dispose +// ND2015:DontCallYourMethodDispose + +warnif count > 0 + +// Activate this rule only when IDisposable is resolved in third-party code +// else this rule might produce false positives. +let thirdPartyDisposable = ThirdParty.Types.WithFullName("System.IDisposable").FirstOrDefault() +where thirdPartyDisposable != null + +from m in JustMyCode.Methods.WithSimpleName("Dispose") +where !m.ParentType.Implement("System.IDisposable".AllowNoMatch()) + && m.OverriddensBase.Count() == 0 // Can't change the name of an override + && !m.IsNewSlot // new slot also means override of an intreface method +select new { + m, + Debt = 15.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// In .NET programming, the identifier *Dispose* should be kept +// only for implementations of *System.IDisposable*. +// +// This rule warns when a method is named *Dispose()*, +// but the parent type doesn't implement *System.IDisposable*. +// + +// +// To fix a violation of this rule, +// either make the parent type implements *System.IDisposable*, +// or rename the *Dispose()* method with another identifier like: +// *Close() Terminate() Finish() Quit() Exit() Unlock() ShutDown()*… +//]]> + Methods prefixed with 'Try' should return a boolean +// ND2016:MethodsPrefixedWithTryShouldReturnABoolean + +warnif count > 0 +from m in Application.Methods where + m.SimpleNameLike("^Try") && + m.ReturnType != null && + m.ReturnType.FullName != "System.Boolean" +select new { + m, + m.ReturnType, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// When a method has a name prefixed with **Try**, it is expected that +// it returns a *boolean*, that reflects the method execution status, +// *success* or *failure*. +// +// Such method usually returns a result through an *out parameter*. +// For example: *System.Int32.TryParse(int,out string):bool* +// + +// +// To fix a violation of this rule, +// Rename the method, or transform it into an operation that can fail. +//]]> + Properties and fields that represent a collection of items should be named Items. +// ND2017:PropertiesAndFieldsThatRepresentACollectionOfItemsShouldBeNamedItems + +warnif count > 0 + +let collectionTypes = Types.Where(t => + t.Implement("System.Collections.Generic.IEnumerable".AllowNoMatch()) && + t.IsGeneric && + t.FullName != "System.Collections.Generic.IDictionary" && + !t.Implement("System.Collections.Generic.IDictionary".AllowNoMatch()) && + t.FullName != "System.Collections.Generic.IReadOnlyDictionary" && + !t.Implement("System.Collections.Generic.IReadOnlyDictionary".AllowNoMatch())).ToHashSetEx() + +let properties = from m in JustMyCode.Methods +where (m.IsPropertyGetter || m.IsPropertySetter) && + collectionTypes.Contains(m.ReturnType) +select new { Member = (IMember)m, Type = m.ReturnType } + +let fields = from f in JustMyCode.Fields +where collectionTypes.Contains(f.FieldType) +select new { Member = (IMember)f, Type = f.FieldType } + +from pair in properties.Concat(fields) +let identifier = pair.Member.SimpleName +where !identifier.EndsWith("s") && + !identifier.Contains('<') // Remove potential generated fields like backing fields, and also potential generated properties accessors + +let identifierRefined = identifier.Replace("get_", "").Replace("set_", "") +let words = identifierRefined.GetWords() +where !words.Any(word => + word.EndsWith("s") || // Don't warn if any word ends with an s + word == "Empty") // Don't warn if any word in the identifier is Empty + +select new { pair.Member, pair.Type } + +// +// A good practice to make the code more readable and more predictable +// is to name properties and fields typed with a collection of *items* +// with the plural form of *Items*. +// +// Depending on the domain of your application, a proper identifier could be +// *NewDirectories*, *Words*, *Values*, *UpdatedDates*. +// +// Also this rule doesn't warn when any word in the identifier ends with an *s*. +// This way identifiers like **TasksToRun**, **KeysDisabled**, **VersionsSupported**, +// **ChildrenFilesPath**, **DatedValuesDescending**, **ApplicationNodesChanged** +// or **ListOfElementsInResult** are valid and won't be seen as violations. +// +// Moreover this rule won't warn for a field or property with an identifier +// that contain the word **Empty**. +// This is a common pattern to define an immutable and empty collection instance +// shared. +// +// Before inspecting properties and fields, this rule gathers +// application and third-party collection types that might be returned +// by a property or a field. To do so this rule searches types that implement +// *IEnumerable* except: +// +// - Non generic types: Often a non generic type is not seen as a collection. +// For example *System.String* implements *IEnumerable*, but a string +// is rarely named as a collection of characters. In others words, +// we have much more strings in our program named like *FirstName* +// than named like *EndingCharacters*. +// +// - Dictionaries types: A dictionary is more than a collection of pairs, +// it is a mapping from one domain to another. A common practice is to suffix +// the name of a dictionary with *Map* *Table* or *Dictionary*, +// although often dictionaries names satify this rule with names like +// *GuidsToPersons* or *PersonsByNames*. +// + +// +// Just rename the fields and properties accordingly, +// by making plural the word in the identifier +// that describes best the *items* in the collection. +// +// For example: +// +// - **ListOfDir** can be renamed **Directories**. +// +// - **Children** can be renamed **ChildrenItems** +// +// - **QueueForCache** can be renamed **QueueOfItemsForCache** +//]]> + DDD ubiquitous language check +// ND2018:DDDUbiquitousLanguageCheck + +warnif count > 0 + +// Update to your core domain namespace(s) +let coreDomainNamespaces = Application.Namespaces.WithNameLike("TrainTrain.Domain") + +// Update your vocabulary list +let vocabulary = new [] { +"Train", "Coach", "Coaches", "Seat", "Seats", +"Reservation", "Fulfilled", "Booking", "Book", "Reserve", "Confirm" +}.ToHashSetEx() + +let technicalWords = new [] { "get", "set", "Get", "Set", "Add" }.ToHashSetEx() +let multiWordsVocabulary = vocabulary.Where(w => w.GetWords().Length >= 2) + +// Append multi-words words in vocabulary +let multiWordsWords = multiWordsVocabulary.SelectMany(w => w.GetWords()) +let vocabulary2 = vocabulary.Concat(multiWordsWords).ToHashSetEx() +let vocabulary3 = vocabulary2.Concat(technicalWords).ToHashSetEx() + +from ce in coreDomainNamespaces.ChildTypesAndMembers() +let tokens = ce.SimpleName.GetWords().Select(w => w.FirstCharToUpper()).ToArray() + +where !(ce.IsMethod && ce.AsMethod.IsConstructor) && // No vocabulary in ctor + !(ce.IsField && ce.AsField.IsGeneratedByCompiler) && // Remove compiler generated backing fields, their vocabulary is in property + !(vocabulary3.Any(vocable => tokens.Contains(vocable))) + +let wordsNotInVocabulary = tokens.Where(w => !(vocabulary2.Contains(w) && string.IsNullOrEmpty(w))).ToArray() + +select new { ce, + wordsNotInVocabulary = string.Join(", ",wordsNotInVocabulary) +} + +// +// The language used in identifiers of classes, methods and fields of the **core domain**, +// should be based on the **Domain Model**. +// This constraint is known as **ubiquitous language** in **Domain Driven Design (DDD)** +// and it reflects the need to be rigorous with naming, +// since software doesn't cope well with ambiguity. +// +// This rule is disabled per default +// because its source code needs to be customized to work, +// both with the **core domain** namespace name +// (that contains classes and types to be checked), +// and with the list of domain language terms. +// +// If a term needs to be used both with singular and plural forms, +// both forms need to be mentioned, +// like **Seat** and **Seats** for example. +// Notice that this default rule is related with the other default rule +// *Properties and fields that represent a collection of items should be named Items* +// defined in the *Naming Convention* group. +// +// This rule implementation relies on the NDepend API +// **ExtensionMethodsString.GetWords(this string identifier)** +// extension method that extracts terms +// from classes, methods and fields identifiers in a smart way. +// + +// +// For each violation, this rule provides the list of **words not in vocabulary**. +// +// To fix a violation of this rule, either rename the concerned code element +// or update the domain language terms list defined in this rule source code, +// with the missing term(s). +//]]> + Avoid fields with same name in class hierarchy +// ND2019:AvoidFieldsWithSameNameInClassHierarchy + +warnif count > 0 + +// Optimization: only consider fields of derived classes +let derivedClasses = Application.Types.Where(t => + t.IsClass && + !t.IsStatic && + t.DepthOfInheritance > 1 && + t.BaseClasses.ChildFields().Any()) + +from f in derivedClasses.ChildFields() +where JustMyCode.Contains(f) // Make sure it is fixable by targeting only JustMyCode fields +let fieldsOfBaseClassesWithSameName = + f.ParentType + .BaseClasses + .ChildFields() + .Where(cf => cf.Name == f.Name && JustMyCode.Contains(cf)) +where fieldsOfBaseClassesWithSameName.Any() + +select new { + f, + fieldsOfBaseClassesWithSameName, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule matches a field whose name is already used by +// another field in the base class hierarchy. +// +// The C# and VB.NET compilers allow this situation even +// if the base class field is visible to derived classes +// with a visibility different than *private*. +// +// However this situation is certainly a sign of +// an erroneous attempt to redefine a field in a derived class. +// + +// +// Check if the field in the derived class is indeed a redefinition +// of the base class field. Check also that both fields types +// corresponds. If fields are static, double check that only a single +// instance of the referenced object is needed. If all checks are positive +// delete the derived class field and make sure that the base class +// field is visible to derived classes with the *protected* visibility. +// +// If no, rename the field in the derived class and be very careful +// in renaming all usages of this field, they might be related +// with the base class field. +//]]> + Avoid various capitalizations for method name +// ND2020:AvoidVariousCapitalizationsForMethodName + +warnif count > 0 + +let lookup = JustMyCode.Methods.ToLookup(m => m.SimpleName.ToLower()) +from grouping in lookup +where grouping.Count() > 1 +let name = grouping.First().SimpleName +let method = grouping.FirstOrDefault(m => m.SimpleName != name) +where method != null +let nbCapitalizations = grouping.ToLookup(m => m.SimpleName).Count + +// Increase the severity if various capitalizations are found in same class +let differentCapitalizationInSameType = + grouping.ToLookup(m => m.ParentType) + .Any(groupingPerType => groupingPerType.ToLookup(m => m.SimpleName).Count > 1) + +select new { + method, + methods = grouping.ToArray(), + nbCapitalizations, + Debt = (nbCapitalizations * 6).ToMinutes().ToDebt(), + Severity = differentCapitalizationInSameType ? Severity.Critical : Severity.Medium +} + +// +// This rule matches application methods whose names differ +// only in capitalization. +// +// With this rule you'll discover various names like +// *ProcessOrderId* and *ProcessOrderID*. It is important to +// choose a single capitalization used accross the whole application. +// +// Matched methods are not necessarily declared in the same type. +// However if various capitalizations of a method name are found +// within the same type, the issue severity goes from *Medium* +// to *Critical*. Indeed, this situation is not just a matter +// of convention, it is error-prone. It forces type's clients +// to investigate which version of the method to call. +// + +// +// Choose a single capitalization for the method name +// used accross the whole application. +// +// Or alternatively make the distinction clear by having +// different method names that don't only differ by +// capitalization. +// +// The technical-debt for each issue, the estimated cost +// to fix an issue, is proportional to the number +// of capitalizations found (2 minimum). +//]]> + Controller class name should be suffixed with 'Controller' +// ND2021:ControllerClassNameShouldBeSuffixedController + +warnif count > 0 from t in Application.Types where + (t.DeriveFrom("Microsoft.AspNetCore.Mvc.Controller".AllowNoMatch()) || + t.DeriveFrom("Microsoft.AspNetCore.Mvc.ControllerBase".AllowNoMatch()) || + t.HasAttribute("Microsoft.AspNetCore.Mvc.ControllerAttribute".AllowNoMatch())) + && + + // We use SimpleName, because in case of generic Exception type + // SimpleName suppresses the generic suffix (like ). + !t.SimpleNameLike(@"Controller$") && + !t.SimpleNameLike(@"ControllerBase$") // Allow the second suffix Base + // for base Controller classes. +select new { + t, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule warns about *ASP.NET Controller classes* whose names are not +// suffixed with **Controller**, this is a recommended practice: +// https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/actions +// +// For Controller base classes, the suffix **ControllerBase** +// is also accepted. +// + +// +// Suffix the names of matched Controller classes with **Controller**. +// +]]> + + + Avoid referencing source file out of Visual Studio project directory +// ND2100:AvoidReferencingSourceFileOutOfVisualStudioProjectDirectory + +warnif count > 0 + +from a in Application.Assemblies +where a.VisualStudioProjectFilePath != null +let vsProjDirPathLower = a.VisualStudioProjectFilePath.ParentDirectoryPath.ToString().ToLower() + +from t in a.ChildTypes +where JustMyCode.Contains(t) && t.SourceFileDeclAvailable + +from decl in t.SourceDecls +let sourceFilePathLower = decl.SourceFile.FilePath.ToString().ToLower() +where sourceFilePathLower.IndexOf(vsProjDirPathLower) != 0 +select new { + t, + sourceFilePathLower, + projectFilePath = a.VisualStudioProjectFilePath.ToString(), + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// A source file located outside of the VS project directory can be added through: +// *> Add > Existing Items… > Add As Link* +// +// Doing so can be used to share types definitions across several assemblies. +// This provokes type duplication at binary level. +// Hence maintainability is degraded and subtle versioning bug can appear. +// +// This rule matches types whose source files are not declared under the +// directory that contains the related Visual Studio project file, or under +// any sub-directory of this directory. +// +// This practice can be tolerated for certain types shared across executable assemblies. +// Such type can be responsible for startup related concerns, +// such as registering custom assembly resolving handlers or +// checking the .NET Framework version before loading any custom library. +// + +// +// To fix a violation of this rule, prefer referencing from a VS project +// only source files defined in sub-directories of the VS project file location. +// +// By default issues of this rule have a **Low** severity +// because they reflect more an advice than a problem. +//]]> + Avoid duplicating a type definition across assemblies +// ND2101:AvoidDuplicatingATypeDefinitionAcrossAssemblies + +warnif count > 0 + +let groups = Application.Assemblies + // Don't match types in ASP.NET Core generated assemblies + // whose name is suffixed with '.Views' + .Where(a => !a.Name.ToLower().EndsWith(".views")) + .ChildTypes() + .Where(t => !t.IsGeneratedByCompiler && + // Types created by the test infrastructure + t.FullName != "AutoGeneratedProgram") + .GroupBy(t => t.FullName) +from @group in groups +where @group.Count() > 1 + +// Tricky: This rule is executed on both current snapshot and baseline snapshot (if any). +// Taking t as @group.First() could return any type in the group, and potentially +// it can return different types for current and baseline snapshot. +// As a result issues wouldn't corresponds (since types are different) and the user +// would see issues of this rule as a couple of issues added and removed. +// Ordering types by parent assembly name and then taking the first type, discards this risk. +let types = @group.OrderBy(t => t.ParentAssembly.Name) + +select new { + t = types.First(), + // In the 'types' column, make sure to group matched types by parent assemblies. + typesDefs = types.ToArray(), + Debt = 15.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// A source file located outside of the VS project directory can be added through: +// *> Add > Existing Items… > Add As Link* +// +// This rule warns about using this feature to share code across several assemblies. +// This provokes type duplication at binary level. +// Hence maintainability is degraded and subtle versioning bug can appear. +// +// Each match represents a type duplicated. Unfolding the types in the column +// **typesDefs** will show the multiple definitions across multiple assemblies. +// +// This practice can be tolerated for certain types shared across executable assemblies. +// Such type can be responsible for startup related concerns, +// such as registering custom assembly resolving handlers or +// checking the .NET Framework version before loading any custom library. +// + +// +// To fix a violation of this rule, prefer sharing types through DLLs. +//]]> + Avoid defining multiple types in a source file +// ND2102:AvoidDefiningMultipleTypesInASourceFile + +warnif count > 0 + +// Build a lookup indexed by source files, values being a sequence of types defined in the source file. +let lookup = JustMyCode.Types + // When a source file is referenced by several assemblies, + // type(s) contained in the source file are seen as distinct types, both by the CLR and by NDepend. + // This Distinct clause based on type full-name (type name prefixed with namespace) + // avoids matching the multiple versions of such type. + .Distinct(t => t.FullName) + .Where(t => t.SourceFileDeclAvailable && + // except enumerations, nested types and types generated by compilers! + !t.IsEnumeration && + !t.IsNested && + !t.IsGeneratedByCompiler) + // We use multi-key, since a type can be declared in multiple source files. + .ToMultiKeyLookup(t => t.SourceDecls.Select(d => d.SourceFile)) + +from @group in lookup where @group.Count() > 1 + let sourceFile = @group.Key + + // CQLinq doesn't let indexing result with sourceFile + // so we choose a typeIndex in types, + // preferably the type that has the file name. + let typeWithSourceFileName = @group.FirstOrDefault(t => t.SimpleName == sourceFile.FileNameWithoutExtension) + let typeIndex = typeWithSourceFileName ?? @group.First() + +// Don't warn for types with at least one declaration in a generated file +// like the UWP generated file 'App.g.i.cs' +where !sourceFile.FilePathString.ToLower().Contains(".g.") + +select new { + typeIndex, + TypesInSourceFile = @group as IEnumerable, + SourceFilePathString = sourceFile.FilePathString, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// Defining multiple types in a single source file decreases code readability, +// because developers are used to see all types in a namespace, +// when expanding a folder in the *Visual Studio Solution Explorer*. +// Also doing so, leads to source files with too many lines. +// +// Each match of this rule is a source file that contains several types +// definitions, indexed by one of those types, preferably the one with +// the same name than the source file name without file extension, if any. +// + +// +// To fix a violation of this rule, create a source file for each type. +// +// By default issues of this rule have a **Low** severity +// because they reflect more an advice than a problem. +//]]> + Namespace name should correspond to file location +// ND2103:NamespaceNameShouldCorrespondToFileLocation + +warnif count > 0 + +from a in Application.Assemblies +let assemblyName = a.Name + +from n in a.ChildNamespaces +let namespaceName = n.Name + +// Build the dirShouldContain string +// and then we check which source file path contains dirShouldContain or not +let dirShouldContain = ( + // namespaceName starts with assembly name => gets only components after the assembly name + namespaceName.StartsWith(assemblyName) + ? namespaceName.Substring(assemblyName.Length) + + // namespaceName contains assembly name => gets only components after the assembly name + : namespaceName.Contains("." + assemblyName) + ? namespaceName.Substring(n.Name.IndexOf("." + assemblyName)) + + // namespaceName has more than one part => gets only components after the first part + : namespaceName.Contains(".") + ? namespaceName.Substring(n.Name.IndexOf(".")) + : namespaceName) + + // Replace dots and underscore by spaces in namespace name + .Replace('.', ' ') + .Replace('_', ' ') + +where dirShouldContain.Length > 0 + +// Look at source file decl of JustMyCode type's declared in the namespace 'n' +from t in n.ChildTypes +where JustMyCode.Contains(t) && t.SourceFileDeclAvailable + +let sourceDeclConcerned = (from decl in t.SourceDecls + let sourceFilePath = decl.SourceFile.FilePath.ToString() + // Replace dots, path separators, minus and underscores + // by spaces in source files names + where !sourceFilePath + .Replace('.',' ').Replace('\\',' ') + .Replace('-',' ').Replace('_',' ') + .Contains(dirShouldContain) + select sourceFilePath).ToArray() +where sourceDeclConcerned.Length > 0 + +let justACaseSensitiveIssue = sourceDeclConcerned[0].ToLower() + .Replace('.',' ').Replace('\\',' ') + .Replace('-',' ').Replace('_',' ') + .Contains(dirShouldContain.ToLower()) + +select new { + t, + dirShouldContain, + sourceFilePath = sourceDeclConcerned[0], + nbSourceDeclConcerned = sourceDeclConcerned.Length, + justACaseSensitiveIssue , + Debt = (justACaseSensitiveIssue ? + 1f + sourceDeclConcerned.Length/2f : + 2f + sourceDeclConcerned.Length).ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// For a solid code structure and organization, +// do mirror the namespaces hierarchy and the directories hierarchy containing source files. +// +// Doing so is a widely accepted convention, and not respecting this convention +// will lead to less maintainable and less browsable source code. +// +// This rule matches all types in such source file, whose location doesn't correspond +// to the type parent namespace. If a source file contains several such types (that +// are not necessarily in the same namespace) each type will result in a violation. +// +// If a type is declared in several such source files, the value for the column +// *nbSourceDeclConcerned* in the result, is greater than 1. +// The technical-debt per issue is proportional to *nbSourceDeclConcerned*. +// +// Notice that namespaces and directories names comparison is **case-sensitive**. +// A boolean *justACaseSensitiveIssue* indicates if it is just a case-sensitive issue, +// in which case the technical-debt is divided by two. +// + +// +// To fix a violation of this rule, make sure that the type parent namespace and +// the directory sub-paths that contains the type source file, are mirrored. +// +// Make sure to first check the boolean *justACaseSensitiveIssue*, in which case +// the issue is easier to fix. +// +]]> + Types with source files stored in the same directory, should be declared in the same namespace +// ND2104:TypesWithSourceFilesStoredInTheSameDirectoryShouldBeDeclaredInTheSameNamespace + +warnif count > 0 + +// Group JustMyCode types in a lookup +// where groups are keyed with directories that contain the types' source file(s). +// Note that a type can be contained in several groups +// if it is declared in several source files stored in different directories. +let lookup = JustMyCode.Types.Where(t => t.SourceFileDeclAvailable) + .ToMultiKeyLookup( + t => t.SourceDecls.Select( + decl => decl.SourceFile.FilePath.ParentDirectoryPath).Distinct() + ) + +from groupOfTypes in lookup +let parentNamespaces = groupOfTypes.ParentNamespaces() + +// Select group of types (with source files stored in the same directory) … +// … but contained in several namespaces +where parentNamespaces.Count() > 1 + +// mainNamespaces is the namespace that contains many types +// declared in the directory groupOfTypes .key +let mainNamespace = groupOfTypes + .ToLookup(t => t.ParentNamespace) + .OrderByDescending(g => g.Count()).First().Key + +// Select types with source files stored in the same directory, +// but contained in namespaces different than mainNamespace. +let typesOutOfMainNamespace = groupOfTypes + .Where(t => t.ParentNamespace != mainNamespace && + t.ParentAssembly == mainNamespace.ParentAssembly) + + // Filter types declared on several source files that contain generated methods + // because typically such type contains one or several partial definitions generated. + // These partially generated types would be false positive for the present rule. + .Where(t => t.SourceDecls.Count() == 1 || + t.Methods.Count(m => JustMyCode.Contains(m)) == 0) +where typesOutOfMainNamespace.Count() > 0 + +let typesInMainNamespace = groupOfTypes.Where(t => t.ParentNamespace == mainNamespace) + +select new { + mainNamespace, + typesOutOfMainNamespace, + typesInMainNamespace, + Debt = (2+5*typesOutOfMainNamespace.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// For a solid code structure and organization, do mirror the namespaces +// hierarchy and the directories hierarchy containing source files. +// +// Doing so is a widely accepted convention, and not respecting this convention +// will lead to less maintainable and less browsable code. +// +// Respecting this convention means that types with source files stored in the same directory, +// should be declared in the same namespace. +// +// For each directory that contains several source files, where most types are declared +// in a namespace (what we call the **main namespace**) and a few types are declared +// out of the *main namespace*, this code rule matches: +// +// • The *main namespace* +// +// • **typesOutOfMainNamespace**: Types declared in source files in the *main namespace*'s directory +// but that are not in the *main namespace*. +// +// • *typesInMainNamespace*: And for informational purposes, types declared in source files in the +// *main namespace*'s directory, and that are in the *main namespace*. +// + +// +// Violations of this rule are types in the *typesOutOfMainNamespace* column. +// Typically such type … +// +// • … is contained in the wrong namespace but its source file is stored in the right directory. +// In such situation the type should be contained in *main namespace*. +// +// • … is contained in the right namespace but its source file is stored in the wrong directory +// In such situation the source file of the type must be moved to the proper parent namespace directory. +// +// • … is declared in multiple source files, stored in different directories. +// In such situation it is preferable that all source files are stored in a single directory. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 2 minutes plus 5 minutes per type in *typesOutOfMainNamespace*. +//]]> + Types declared in the same namespace, should have their source files stored in the same directory +// ND2105:TypesDeclaredInTheSameNamespaceShouldHaveTheirSourceFilesStoredInTheSameDirectory + +warnif count > 0 +from @namespace in Application.Namespaces + +// Group types of @namespace in a lookup +// where groups are keyed with directories that contain the types' source file(s). +// Note that a type can be contained in several groups +// if it is declared in several source files stored in different directories. +let lookup = @namespace.ChildTypes.Where( + t => t.SourceFileDeclAvailable && + JustMyCode.Contains(t) && + // Don't match ASP.NET application types declared in Global.asax file + // that typically are in the root directory of the VS project. + t.SourceDecls.First().SourceFile.FileNameWithoutExtension.ToLower() != "global.asax") + .ToMultiKeyLookup( + t => t.SourceDecls.Select( + decl => decl.SourceFile.FilePath.ParentDirectoryPath).Distinct() + ) + +// Are types of @namespaces declared in more than one directory? +where lookup.Count > 1 + +// Infer the main folder, preferably the one that has the same name as the namespace. +let dirs = lookup.Select(types => types.Key) +let mainDirNullable = dirs.Where(d => d.DirectoryName == @namespace.SimpleName).FirstOrDefault() +let mainDir = mainDirNullable ?? dirs.First() + +// Types declared out of mainDir, are types in group of types declared in a directory different than mainDir! +let typesDeclaredOutOfMainDir = + lookup.Where(types => types.Key != mainDir) + .SelectMany(types => types) + + // Filter types declared on several source files that contain generated methods + // because typically such type contains one or several partial definitions generated. + // These partially generated types would be false positive for the present rule. + .Where(t => t.SourceDecls.Count() == 1 || + t.Methods.Count(m => JustMyCode.Contains(m)) == 0) + +where typesDeclaredOutOfMainDir.Count() > 0 && + + // Don't warn for types with at least one declaration in a generated file + // like the UWP generated file 'App.g.i.cs' + !typesDeclaredOutOfMainDir + .Where(t => t.SourceFileDeclAvailable) + .SelectMany(t => t.SourceDecls.Select(sd => sd.SourceFile.FilePathString.ToLower())) + .Any(fps => fps.Contains(".g.")) + +let typesDeclaredInMainDir = + lookup.Where(types => types.Key == mainDir) + .SelectMany(types => types) + +select new { + @namespace, + typesDeclaredOutOfMainDir, + mainDir = mainDir.ToString(), + typesDeclaredInMainDir, + Debt = (2+5*typesDeclaredOutOfMainDir.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// For a solid code structure and organization, +// do mirror the namespaces hierarchy and the directories hierarchy containing source files. +// +// Doing so is a widely accepted convention, and not respecting this convention +// will lead to less maintainable and less browsable code. +// +// Respecting this convention means that types declared in the same namespace, +// should have their source files stored in the same directory. +// +// For each namespace that contains types whose source files +// are declared in several directories, infer the **main directory**, +// the directory that naturally hosts source files of types, +// preferably the directory whose name corresponds with the namespace +// name. In this context, this code rule matches: +// +// • The namespace +// +// • **typesDeclaredOutOfMainDir**: types in the namespace whose source files +// are stored out of the *main directory*. +// +// • The *main directory* +// +// • *typesDeclaredInMainDir*: for informational purposes, types declared +// in the namespace, whose source files are stored in the *main directory*. +// + +// +// Violations of this rule are types in the **typesDeclaredOutOfMainDir** column. +// Typically such type… +// +// • … is contained in the wrong namespace but its source file is stored in the right directory. +// In such situation the type should be contained in the namespace corresponding to +// the parent directory. +// +// • … is contained in the right namespace but its source file is stored in the wrong directory. +// In such situation the source file of the type must be moved to the *main directory*. +// +// • … is declared in multiple source files, stored in different directories. +// In such situation it is preferable that all source files are stored in a single directory. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 2 minutes plus 5 minutes per type in *typesDeclaredOutOfMainDir*. +//]]> + + + + Mark ISerializable types with SerializableAttribute +// ND2200:MarkISerializableTypesWithSerializableAttribute + +warnif count > 0 + +from t in Application.Types where + t.IsPublic && + !t.IsDelegate && + !t.IsExceptionClass && // Don't match exceptions, since the Exception class + // implements ISerializable, this would generate + // too many false positives. + t.Implement ("System.Runtime.Serialization.ISerializable".AllowNoMatch()) && + !t.HasAttribute ("System.SerializableAttribute".AllowNoMatch()) + +select new { + t, + t.NbLinesOfCode, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// To be recognized by the CLR as serializable, +// types must be marked with the *SerializableAttribute* +// attribute even if the type uses a custom +// serialization routine through implementation of +// the *ISerializable* interface. +// +// This rule matches types that implement *ISerializable* and +// that are not tagged with *SerializableAttribute*. +// + +// +// To fix a violation of this rule, tag the matched type +// with *SerializableAttribute* . +//]]> + Mark assemblies with CLSCompliant (deprecated) +// ND2201:MarkAssembliesWithCLSCompliant + +warnif count > 0 from a in Application.Assemblies where + !a.HasAttribute ("System.CLSCompliantAttribute".AllowNoMatch()) +select new { + a, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule has been deprecated and, as a consequence, it is disabled by default. +// Feel free to re-enable it if it makes sense in your dev environment. +// +// The *Common Language Specification* (CLS) defines naming restrictions, +// data types, and rules to which assemblies must conform if they are to +// be used across programming languages. Good design dictates that all +// assemblies explicitly indicate CLS compliance with **CLSCompliantAttribute**. +// If the attribute is not present on an assembly, the assembly is not compliant. +// +// Notice that it is possible for a CLS-compliant assembly to contain types or +// type members that are not compliant. +// +// This rule matches assemblies that are not tagged with +// **System.CLSCompliantAttribute**. +// + +// +// To fix a violation of this rule, tag the assembly with *CLSCompliantAttribute*. +// +// Instead of marking the whole assembly as non-compliant, you should determine +// which type or type members are not compliant and mark these elements as such. +// If possible, you should provide a CLS-compliant alternative for non-compliant +// members so that the widest possible audience can access all the functionality +// of your assembly. +//]]> + Mark assemblies with ComVisible (deprecated) +// ND2202:MarkAssembliesWithComVisible + +warnif count > 0 from a in Application.Assemblies where + !a.HasAttribute ("System.Runtime.InteropServices.ComVisibleAttribute".AllowNoMatch()) +select new { + a, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule has been deprecated and, as a consequence, it is disabled by default. +// Feel free to re-enable it if it makes sense in your dev environment. +// +// The **ComVisibleAttribute** attribute determines how COM clients access +// managed code. Good design dictates that assemblies explicitly indicate +// COM visibility. COM visibility can be set for a whole assembly and then +// overridden for individual types and type members. If the attribute is not +// present, the contents of the assembly are visible to COM clients. +// +// This rule matches assemblies that are not tagged with +// **System.Runtime.InteropServices.ComVisibleAttribute**. +// + +// +// To fix a violation of this rule, tag the assembly with *ComVisibleAttribute*. +// +// If you do not want the assembly to be visible to COM clients, set the +// attribute value to **false**. +//]]> + Mark attributes with AttributeUsageAttribute +// ND2203:MarkAttributesWithAttributeUsageAttribute + +warnif count > 0 +from t in JustMyCode.Types where + + t.DeriveFrom ("System.Attribute".AllowNoMatch()) +&& !t.HasAttribute ("System.AttributeUsageAttribute".AllowNoMatch()) + +// AttributeUsageAttribute can be deferred to classes that derive from an abstract attribute class +&& !t.IsAbstract + +// AttributeUsageAttribute can de inherited from a base attribute class (that is not System.Attribute) +&& !t.BaseClasses.Any(c => + c.FullName != "System.Attribute" && + c.HasAttribute ("System.AttributeUsageAttribute".AllowNoMatch())) + +select new { + t, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// When you define a custom attribute, mark it by using **AttributeUsageAttribute** +// to indicate where in the source code the custom attribute can be applied. The +// meaning and intended usage of an attribute will determine its valid locations +// in code. For example, you might define an attribute that identifies the person +// who is responsible for maintaining and enhancing each type in a library, and +// that responsibility is always assigned at the type level. In this case, compilers +// should enable the attribute on classes, enumerations, and interfaces, but should +// not enable it on methods, events, or properties. Organizational policies and +// procedures would dictate whether the attribute should be enabled on assemblies. +// +// The **System.AttributeTargets** enumeration defines the targets that you can +// specify for a custom attribute. If you omit *AttributeUsageAttribute*, your +// custom attribute will be valid for all targets, as defined by the **All** value of +// *AttributeTargets* enumeration. +// +// This rule matches attribute classes that are not tagged with +// **System.AttributeUsageAttribute**. +// +// Abstract attribute classes are not matched since AttributeUsageAttribute +// can be deferred to derived classes. +// +// Attribute classes that have a base class tagged with AttributeUsageAttribute +// are not matched since in this case attribute usage is inherited. +// + +// +// To fix a violation of this rule, specify targets for the attribute by using +// *AttributeUsageAttribute* with the proper *AttributeTargets* values. +//]]> + Remove calls to GC.Collect() +// ND2204:RemoveCallsToGCCollect + +warnif count > 0 + +let gcCollectMethods = ThirdParty.Methods.WithFullNameWildcardMatch( + "System.GC.Collect(*)").ToHashSetEx() + +from m in Application.Methods.UsingAny(gcCollectMethods) +select new { + m, + gcCollectMethodCalled = m.MethodsCalled.Intersect(gcCollectMethods), + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// It is preferable to avoid calling **GC.Collect()** +// explicitly in order to avoid some performance pitfall. +// +// More in information on this here: +// http://blogs.msdn.com/ricom/archive/2004/11/29/271829.aspx +// +// This rule matches application methods that call an +// overload of the method *GC.Collect()*. +// + +// +// Remove matched calls to *GC.Collect()*. +//]]> + Don't call GC.Collect() without calling GC.WaitForPendingFinalizers() +// ND2205:DontCallGCCollectWithoutCallingGCWaitForPendingFinalizers + +warnif count > 0 + +let gcCollectMethods = ThirdParty.Methods.WithFullNameWildcardMatch( + "System.GC.Collect(*)").ToHashSetEx() + +from m in Application.Methods.UsingAny(gcCollectMethods) where + !m.IsUsing ("System.GC.WaitForPendingFinalizers()".AllowNoMatch()) +select new { + m, + gcCollectMethodCalled = m.MethodsCalled.Intersect(gcCollectMethods), + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// It is preferable to avoid calling **GC.Collect()** +// explicitly in order to avoid some performance +// pitfall. This situation is checked through the +// default rules: *Remove calls to GC.Collect()* +// +// But if you wish to call *GC.Collect()* anyway, +// you must do it this way: +// +// GC.Collect(); +// +// GC.WaitForPendingFinalizers(); +// +// GC.Collect(); +// +// To make sure that finalizer got executed, and +// object with finalizer got cleaned properly. +// +// This rule matches application methods that call an +// overload of the method *GC.Collect()*, without calling +// *GC.WaitForPendingFinalizers()*. +// + +// +// To fix a violation of this rule, if you really +// need to call *GC.Collect()*, make sure to call +// *GC.WaitForPendingFinalizers()* properly. +//]]> + Enum Storage should be Int32 +// ND2206:EnumStorageShouldBeInt32 + +warnif count > 0 from f in JustMyCode.Fields where + f.ParentType.IsEnumeration && + f.Name == @"value__" && + f.FieldType != null && + f.FieldType.FullName != "System.Int32" && + !f.IsThirdParty +select new { + f, + f.SizeOfInst, + f.FieldType, + Debt = 7.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// An enumeration is a value type that defines a set of related named constants. +// By default, the **System.Int32** data type is used to store the constant value. +// +// Even though you can change this underlying type, it is not necessary or +// recommended for most scenarios. Note that *no significant performance gain* is +// achieved by using a data type that is smaller than *Int32*. If you cannot use +// the default data type, you should use one of the Common Language System +// (CLS)-compliant integral types, *Byte*, *Int16*, *Int32*, or *Int64* to make +// sure that all values of the enumeration can be represented in CLS-compliant +// programming languages. +// +// This rule matches enumerations whose underlying type used to store +// values is not *System.Int32*. +// + +// +// To fix a violation of this rule, unless size or compatibility issues exist, +// use *Int32*. For situations where *Int32* is not large enough to hold the values, +// use *Int64*. If backward compatibility requires a smaller data type, use +// *Byte* or *Int16*. +//]]> + Do not raise too general exception types +// ND2207:DoNotRaiseTooGeneralExceptionTypes + +warnif count > 0 + +let tooGeneralExceptionTypes = ThirdParty.Types.WithFullNameIn( + "System.Exception", + "System.ApplicationException", + "System.SystemException") + +from m in JustMyCode.Methods.ThatCreateAny(tooGeneralExceptionTypes) +// Make sure we don't match constructor of exception types +// that actually instantiate System.Exception. +where !m.IsConstructor || tooGeneralExceptionTypes.All(t => !m.ParentType.DeriveFrom(t)) +let exceptionsCreated = tooGeneralExceptionTypes.Where(t => m.IsUsing(t)) +select new { + m, + exceptionsCreated, + Debt = (15 + 5*exceptionsCreated.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// The following exception types are too general +// to provide sufficient information to the user: +// +// • System.Exception +// +// • System.ApplicationException +// +// • System.SystemException +// +// If you throw such a general exception type in a library or framework, +// it forces consumers to catch all exceptions, +// including unknown exceptions that they do not know how to handle. +// +// This rule matches methods that create an instance of +// such general exception class. +// + +// +// To fix a violation of this rule, change the type of the thrown exception +// to either a more derived type that already exists in the framework, +// or create your own type that derives from *System.Exception*. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 15 minutes per method matched, plus 5 minutes per too general +// exception types instantiated by the method. +//]]> + Do not raise reserved exception types +// ND2208:DoNotRaiseReservedExceptionTypes + +warnif count > 0 + +let reservedExceptions = ThirdParty.Types.WithFullNameIn( + "System.NullReferenceException", + "System.ExecutionEngineException", + "System.IndexOutOfRangeException", + "System.OutOfMemoryException", + "System.StackOverflowException", + "System.InvalidProgramException", + "System.AccessViolationException", + "System.CannotUnloadAppDomainException", + "System.BadImageFormatException", + "System.DataMisalignedException") + +from m in Application.Methods.ThatCreateAny(reservedExceptions) +let reservedExceptionsCreated = reservedExceptions.Where(t => m.IsUsing(t)) +select new { + m, + reservedExceptionsCreated, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// The following exception types are reserved +// and should be thrown only by the Common Language Runtime: +// +// • System.ExecutionEngineException +// +// • System.IndexOutOfRangeException +// +// • System.NullReferenceException +// +// • System.OutOfMemoryException +// +// • System.StackOverflowException +// +// • System.InvalidProgramException +// +// • System.AccessViolationException +// +// • System.CannotUnloadAppDomainException +// +// • System.BadImageFormatException +// +// • System.DataMisalignedException +// +// Do not throw an exception of such reserved type. +// +// This rule matches methods that create an instance of +// such reserved exception class. +// + +// +// To fix a violation of this rule, change the type of the +// thrown exception to a specific type that is not one of +// the reserved types. +// +// Concerning the particular case of a method throwing +// *System.NullReferenceException*, often the fix will be either +// to throw instead *System.ArgumentNullException*, either to +// use a contract (through MS Code Contracts API or *Debug.Assert()*) +// to signify that a null reference at that point can only be +// the consequence of a bug. +// +// More generally the idea of using a contract instead of throwing +// an exception in case of *corrupted state / bug consequence* detected +// is a powerful idea. It replaces a behavior (throwing exception) +// with a declarative assertion that basically means: at that point a bug +// somehow provoqued the detected corrupted state and continuing +// any processing from now is potentially harmful. The process should be +// shutdown and the circonstances of the failure should be reported +// as a bug to the product team. +//]]> + Uri fields should be of type System.Uri +// ND2209:UriFieldsShouldBeOfTypeSystemUri + +warnif count > 0 from f in Application.Fields where + (f.NameLike (@"Uri$") || + f.NameLike (@"Url$")) && + f.FieldType != null && + f.FieldType.FullName != "System.Uri" +select new { + f, + f.FieldType, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// A field with the name ending with *'Uri'* or *'Url'* is deemed +// to represent a *Uniform Resource Identifier or Locator*. +// Such field should be of type **System.Uri**. +// +// This rule matches fields with the name ending with *'Uri'* or +// *'Url'* that are not typed with *System.Uri*. +// + +// +// Rename the field, or change the field type to *System.Uri*. +// +// By default issues of this rule have a **Low** severity +// because they reflect more an advice than a problem. +//]]> + Types should not extend System.ApplicationException +// ND2210:TypesShouldNotExtendSystemApplicationException + +warnif count > 0 from t in Application.Types where + t.DeriveFrom("System.ApplicationException".AllowNoMatch()) +select new { + t, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// At .NET Framework version 1 time, it was +// recommended to derive new exceptions from +// *ApplicationException*. +// +// The recommendation has changed and new +// exceptions should derive from **System.Exception** +// or one of its subclasses in the *System* namespace. +// +// This rule matches application exception classes +// that derive from *ApplicationException*. +// + +// +// Make sure that matched exception types, +// derive from **System.Exception** or one of its +// subclasses in the *System* namespace. +//]]> + Don't Implement ICloneable +// ND2211:DontImplementICloneable + +warnif count > 0 +from t in Application.Types +where t.Implement("System.ICloneable".AllowNoMatch()) + + // Don't warn for classes that derive from a class that implements ICloneable + && !t.BaseClasses.Any(c => c.Implement("System.ICloneable".AllowNoMatch())) + +select new { + t, + Debt = 1.ToHours().ToDebt(), + Severity = Severity.High +} + +// +// The interface *System.ICloneable* is proposed since the first version of .NET. +// It is considered as a bad API for several reasons. +// +// First, this interface existed even before .NET 2.0 that introduced generics. +// Hence this interface is not generic and the *Clone()* method returns an *object* +// reference that needs to be *downcasted* to be useful. +// +// Second, this interface doesn't make explicit whether the cloning should be +// **deep** or **shallow**. The difference between the two behaviors +// is fundamental (explanation here https://stackoverflow.com/a/184745/27194). +// Not being able to make the intention explicit leads to confusion. +// +// Third, classes that derive from a class that implements *ICloneable* +// **must** also implement the *Clone()* method, and it is easy to forget +// this constraint. +// +// This rule doesn't warn for classes that derive from a class that +// implements ICloneable. In this case the issue must be fixed +// in the base class, or is maybe not fixable if the base class +// is declared in a third-party assembly. +// + +// +// Don't implement anymore this interface. +// +// You can rename the remaining *Clone()* methods to +// *DeepClone()* or *ShallowClone()* with a typed result. +// +// Or you can propose two custom generic interfaces +// *IDeepCloneable* with the single method *DeepClone():T* +// and *IShallowCloneable* with the single method *ShallowClone():T*. +// +// Finally you can write custom NDepend rules to make sure +// that all classes that derive from a class with a virtual +// clone method, override this method. +//]]> + + + Collection properties should be read only +// ND2300:CollectionPropertiesShouldBeReadOnly + +warnif count > 0 + +// First find collectionTypes +let collectionInterfaces = ThirdParty.Types.WithFullNameIn( + "System.Collections.ICollection", + "System.Collections.Generic.ICollection") +where collectionInterfaces.Count() > 0 +let collectionTypes = Types.ThatImplementAny(collectionInterfaces) + .Union(collectionInterfaces) + .ToHashSetEx() + +// Then find all property setters that have an associated +// getter that returns a collection type. +from propGetter in JustMyCode.Methods.Where( + m => m.IsPropertyGetter && + m.ReturnType != null && + collectionTypes.Contains(m.ReturnType)) + +let propSetter = propGetter.ParentType.Methods.WithSimpleName( + propGetter.SimpleName.Replace("get_","set_") + ).FirstOrDefault() + +where propSetter != null && + !propSetter.IsPrivate && // Ignore private setters since this is private implementation detail + !propSetter.ParentType.IsPrivate && // Ignore setters of private types + + // Ignore properties of serializable types + !propSetter.ParentType.TypesUsed.Any(t1 => t1.IsAttributeClass && t1.ParentNamespace.Name == "Newtonsoft.Json") && + !propSetter.ParentType.HasAttribute("System.Runtime.Serialization.DataContractAttribute".AllowNoMatch()) && + !propSetter.ParentType.HasAttribute("System.Xml.Serialization.XmlRootAttribute".AllowNoMatch()) && + !propSetter.ParentType.HasAttribute("System.SerializableAttribute".AllowNoMatch()) + +select new { + propSetter, + CollectionType = propGetter.ReturnType, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// A writable collection property allows a user to replace the collection with +// a completely different collection. A read-only property stops the collection +// from being replaced but still allows the individual members to be set. If +// replacing the collection is a goal, the preferred *design pattern* is to include +// a method to remove all the elements from the collection and a method to +// re-populate the collection. See the *Clear()* and *AddRange()* methods of the +// *System.Collections.Generic.List* class for an example of this pattern. +// +// Both binary and XML serialization support read-only properties that are +// collections. The *System.Xml.Serialization.XmlSerializer* class has specific +// requirements for types that implement *ICollection* and *System.Collections.IEnumerable* +// in order to be serializable. +// +// This rule matches property setter methods that assign a collection object. +// + +// +// To fix a violation of this rule, make the property read-only and, if +// the design requires it, add methods to clear and re-populate the collection. +//]]> + Don't use .NET 1.x HashTable and ArrayList (deprecated) +// ND2301:DontUseDotNET1HashTableAndArrayList + +warnif count > 0 +let forbiddenTypes = ThirdParty.Types.WithFullNameIn( + "System.Collections.HashTable", + "System.Collections.ArrayList", + "System.Collections.Queue", + "System.Collections.Stack", + "System.Collections.SortedList") +where forbiddenTypes.Count() > 0 +from m in Application.Methods.ThatCreateAny(forbiddenTypes) +let forbiddenTypesUsed = m.MethodsCalled.Where(m1 => m1.IsConstructor && forbiddenTypes.Contains(m1.ParentType)).ParentTypes() +select new { + m, + forbiddenTypesUsed, + Debt = 15.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule has been deprecated and, as a consequence, it is disabled by default. +// Feel free to re-enable it if it makes sense in your dev environment. +// +// This rule warns about application methods that use a non-generic +// collection class, including **ArrayList**, **HashTable**, **Queue**, +// **Stack** or **SortedList**. +// + +// +// **List** should be preferred over **ArrayList**. +// It is generic hence you get strongly typed elements. +// Also, it is faster with *T* as a value types since it avoids boxing. +// +// For the same reasons: +// +// • **Dictionary** should be prevered over **HashTable**. +// +// • **Queue** should be prevered over **Queue**. +// +// • **Stack** should be prevered over **Stack**. +// +// • **SortedDictionary** or **SortedList** should be prevered over **SortedList**. +// +// You can be forced to use *non generic* collections +// because you are using third party code that requires +// working with these classes or because you are +// coding with .NET 1.x, but nowadays this situation should +// question about using newer updates of .NET. +// .NET 1.x is an immature platform conpared to newer .NET +// updates. +//]]> + Caution with List.Contains() +// ND2302:CautionWithListContains + +// warnif count > 0 // This query is n ot a rule per default + +let containsMethods = ThirdParty.Methods.WithFullNameIn( + "System.Collections.Generic.List.Contains(T)", + "System.Collections.Generic.IList.Contains(T)", + "System.Collections.ArrayList.Contains(Object)") + +from m in Application.Methods.UsingAny(containsMethods) +select new { + m, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// This code query matches calls to *List.Contains()* method. +// +// The cost of checking if a list contains an object is proportional +// to the size of the list. In other words it is a *O(N)* operation. +// For large lists and/or frequent calls to *Contains()*, prefer using +// the *System.Collections.Generic.HashSet* class +// where calls to *Contains()* take a constant +// time (*O(0)* operation). +// +// This code query is not a code rule, because more often than not, +// calling *O(N) Contains()* is not a mistake. This code query +// aims at pointing out this potential performance pitfall. +//]]> + Prefer return collection abstraction instead of implementation +// ND2303:PreferReturnCollectionAbstractionInsteadOfImplementation + +// warnif count > 0 // This query is n ot a rule per default + +let implTypes = ThirdParty.Types.WithFullNameIn( + "System.Collections.Generic.List", + "System.Collections.Generic.HashSet", + "System.Collections.Generic.Dictionary") + +from m in Application.Methods.WithReturnTypeIn(implTypes) +select new { + m, + m.ReturnType, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// This code query matches methods that return a +// collection implementation, such as *List* +// *HashSet* or *Dictionary*. +// +// Most often than not, clients of a method don't +// need to know the exact implementation of the +// collection returned. It is preferable to return +// a collection interface such as *IList*, +// *ICollection*, *IEnumerable* or +// *IDictionary*. +// +// Using the collection interface instead of the +// implementation shouldn't applies to all cases, +// hence this code query is not code rule. +//]]> + + + P/Invokes should be static and not be publicly visible +// ND2400:PInvokesShouldBeStaticAndNotBePubliclyVisible + +warnif count > 0 from m in Application.Methods where + !m.IsThirdParty && + (m.HasAttribute ("System.Runtime.InteropServices.DllImportAttribute".AllowNoMatch())) && + ( m.IsPubliclyVisible || + !m.IsStatic) +select new { + m, + m.Visibility, + m.IsStatic, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// Methods that are marked with the **DllImportAttribute** attribute +// (or methods that are defined by using the **Declare** keyword in Visual Basic) +// use **Platform Invocation Services** to access unmanaged code. +// +// Such methods should not be exposed. By keeping these methods *private* or *internal*, +// you make sure that your library cannot be used to breach security by allowing +// callers access to unmanaged APIs that they could not call otherwise. +// +// This rule matches methods tagged with *DllImportAttribute* attribute +// that are declared as *public* or declared as *non-static*. +// + +// +// To fix a violation of this rule, change the access level of the method +// and/or declare it as static. +//]]> + Move P/Invokes to NativeMethods class +// ND2401:MovePInvokesToNativeMethodsClass + +warnif count > 0 from m in Application.Methods where + m.HasAttribute ("System.Runtime.InteropServices.DllImportAttribute".AllowNoMatch()) && + m.ParentType.SimpleName != "NativeMethods" +select new { + m, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// **Platform Invocation methods**, such as those that are marked by using the +// **System.Runtime.InteropServices.DllImportAttribute** attribute, or methods +// that are defined by using the **Declare** keyword in Visual Basic, access +// unmanaged code. These methods should be in one of the following classes: +// +// • **NativeMethods** - This class does not suppress stack walks for unmanaged +// code permission. (*System.Security.SuppressUnmanagedCodeSecurityAttribute* +// must not be applied to this class.) This class is for methods that can be +// used anywhere because a stack walk will be performed. +// +// • **SafeNativeMethods** - This class suppresses stack walks for unmanaged +// code permission. (*System.Security.SuppressUnmanagedCodeSecurityAttribute* +//is applied to this class.) This class is for methods that are safe for anyone +// to call. Callers of these methods are not required to perform a full security +// review to make sure that the usage is secure because the methods are harmless +// for any caller. +// +// • **UnsafeNativeMethods** - This class suppresses stack walks for unmanaged +// code permission. (*System.Security.SuppressUnmanagedCodeSecurityAttribute* +// is applied to this class.) This class is for methods that are potentially +// dangerous. Any caller of these methods must perform a full security review +// to make sure that the usage is secure because no stack walk will be performed. +// +// These classes are declared as *static internal*. The methods in these +// classes are *static* and *internal*. +// +// This rule matches *P/Invoke* methods not declared in such *NativeMethods* +// class. +// + +// +// To fix a violation of this rule, move the method to the appropriate +// **NativeMethods** class. For most applications, moving P/Invokes to a new +// class that is named **NativeMethods** is enough. +//]]> + NativeMethods class should be static and internal +// ND2402:NativeMethodsClassShouldBeStaticAndInternal + +warnif count > 0 from t in Application.Types.WithNameIn( + @"NativeMethods", "SafeNativeMethods", "UnsafeNativeMethods") where + t.IsPublic || !t.IsStatic +select new { + t, + t.Visibility, + t.IsStatic, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// In the description of the default rule *Move P/Invokes to NativeMethods class* +// it is explained that *NativeMethods* classes that host *P/Invoke* methods, +// should be declared as *static* and *internal*. +// +// This code rule warns about *NativeMethods* classes that are not declared +// *static* and *internal*. +// + +// +// Matched *NativeMethods* classes must be declared as *static* and *internal*. +//]]> + + + Don't create threads explicitly +// ND2500:DontCreateThreadsExplicitly + +warnif count > 0 from m in Application.Methods where + m.CreateA ("System.Threading.Thread".AllowNoMatch()) +select new { + m, + Debt = 60.ToMinutes().ToDebt(), + Severity = Severity.Critical +} + +// +// This code rule warns about methods that create *threads* explicitly +// by creating an instance of the class *System.Threading.Thread*. +// +// Prefer using the thread pool instead of creating manually your +// own threads. Threads are costly objects. They take approximately +// 200,000 cycles to create and about 100,000 cycles to destroy. +// By default they reserve 1 Mega Bytes of virtual memory for its +// stack and use 2,000-8,000 cycles for each context switch. +// +// As a consequence, it is preferable to let the thread pool +// recycle threads. +// + +// +// Instead of creating explicitly threads, use the **Task Parralel +// Library** *(TPL)* that relies on the CLR thread pool. +// +// Introduction to TPL: https://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx +// +// TPL and the CLR v4 thread pool: +// http://www.danielmoth.com/Blog/New-And-Improved-CLR-4-Thread-Pool-Engine.aspx +// +// By default issues of this rule have a **Critical** severity +// because creating threads can have severe consequences. +//]]> + Don't use dangerous threading methods +// ND2501:DontUseDangerousThreadingMethods + +warnif count > 0 + +let wrongMethods = ThirdParty.Methods.WithFullNameIn( + + "System.Threading.Thread.Abort()", + "System.Threading.Thread.Abort(Object)", + + "System.Threading.Thread.Sleep(Int32)", + + "System.Threading.Thread.Suspend()", + "System.Threading.Thread.Resume()") + +from m in Application.Methods.UsingAny(wrongMethods) +select new { + m, + suppressCallsTo = m.MethodsCalled.Intersect(wrongMethods), + Debt = 40.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule warns about using the methods +// *Abort()*, *Sleep()*, *Suspend()* or *Resume()* +// declared by the *Thread* class. +// +// • Usage of *Thread.Abort()* is dangerous. +// More information on this here: +// http://www.interact-sw.co.uk/iangblog/2004/11/12/cancellation +// +// • Usage of *Thread.Sleep()* is a sign of +// flawed design. More information on this here: +// http://msmvps.com/blogs/peterritchie/archive/2007/04/26/thread-sleep-is-a-sign-of-a-poorly-designed-program.aspx +// +// • *Suspend()* and *Resume()* are dangerous threading methods, marked as obsolete. +// More information on workaround here: +// http://stackoverflow.com/questions/382173/what-are-alternative-ways-to-suspend-and-resume-a-thread +// + +// +// Suppress calls to *Thread* methods exposed in the +// *suppressCallsTo* column in the rule result. +// +// Use instead facilities offered by the **Task Parralel +// Library** *(TPL)* : +// https://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx +//]]> + Monitor TryEnter/Exit must be both called within the same method +// ND2502:MonitorTryEnterExitMustBeBothCalledWithinTheSameMethod + +warnif count > 0 + +let enterMethods = ThirdParty.Methods.WithFullNameWildcardMatchIn( + "System.Threading.Monitor.Enter(*", + "System.Threading.Monitor.TryEnter(*") + +from m in Application.Methods.UsingAny(enterMethods) +where + !m.IsUsing ("System.Threading.Monitor.Exit(Object)".AllowNoMatch()) +select new { + m, + enterMethodsCalled = m.MethodsCalled.Intersect(enterMethods), + Debt = 20.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule warns when **System.Threading.Monitor** *Enter()* +// (or *TryEnter()*) and *Exit() methods are not called within +// the same method. +// +// Doing so makes the code *less readable*, because it gets harder +// to locate when **critical sections** begin and end. +// +// Also, you expose yourself to complex and error-prone scenarios. +// + +// +// Refactor matched methods to make sure that *Monitor critical +// sections* begin and end within the same method. Basics scenarios +// can be handled through the C# **lock** keyword. Using explicitly +// the class *Monitor* should be left for advanced situations, +// that require calls to methods like *Wait()* and *Pulse()*. +// +// More information on using the *Monitor* class can be found here: +// http://www.codeproject.com/Articles/13453/Practical-NET-and-C-Chapter +//]]> + ReaderWriterLock AcquireLock/ReleaseLock must be both called within the same method +// ND2503:ReaderWriterLockAcquireLockReleaseLockMustBeBothCalledWithinTheSameMethod + +warnif count > 0 + +let acquireLockMethods = ThirdParty.Methods.WithFullNameWildcardMatch( + "System.Threading.ReaderWriterLock.Acquire*Lock(*") + +let releaseLockMethods = ThirdParty.Methods.WithFullNameWildcardMatch( + "System.Threading.ReaderWriterLock.Release*Lock(*") + +from m in Application.Methods.UsingAny(acquireLockMethods) + .Except(Application.Methods.UsingAny(releaseLockMethods)) +select new { + m, + acquireLockMethods = m.MethodsCalled.Intersect(acquireLockMethods), + Debt = 20.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule warns when **System.Threading.ReaderWriterLock** +// acquire and release, reader or writer locks methods are not called +// within the same method. +// +// Doing so makes the code *less readable*, because it gets harder +// to locate when **critical sections** begin and end. +// +// Also, you expose yourself to complex and error-prone scenarios. +// + +// +// Refactor matched methods to make sure that *ReaderWriterLock +// read or write critical sections* begin and end within the +// same method. +//]]> + Don't tag instance fields with ThreadStaticAttribute +// ND2504:DontTagInstanceFieldsWithThreadStaticAttribute + +warnif count > 0 +from f in Application.Fields +where !f.IsStatic && + f.HasAttribute ("System.ThreadStaticAttribute".AllowNoMatch()) +select new { + f, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule warns when the attribute **System.ThreadStaticAttribute** +// is tagging *instance* fields. As explained in documentation, this attribute +// is designed to tag only *static* fields. +// https://msdn.microsoft.com/en-us/library/system.threadstaticattribute +// + +// +// Refactor the code to make sure that all fields tagged with +// *ThreadStaticAttribute* are *static*. +//]]> + Method non-synchronized that read mutable states +// ND2505:MethodNonSynchronizedThatReadMutableStates + +from m in Application.Methods where + (m.ReadsMutableObjectState || m.ReadsMutableTypeState) && + !m.IsUsing ("System.Threading.Monitor".AllowNoMatch()) && + !m.IsUsing ("System.Threading.ReaderWriterLock".AllowNoMatch()) +select new { + m, + mutableFieldsUsed = m.FieldsUsed.Where(f => !f.IsImmutable) +} + +// +// Mutable object states are instance fields that +// can be modified through the lifetime of the object. +// +// Mutable type states are static fields that can be +// modified through the lifetime of the program. +// +// This query lists methods that read mutable state +// without synchronizing access. In the case of +// multi-threaded program, doing so can lead to +// state corruption. +// +// This code query is not a code rule because more often +// than not, a match of this query is not an issue. +//]]> + + + Method should not return concrete XmlNode +// ND2600:MethodShouldNotReturnConcreteXmlNode + +warnif count > 0 + +let concreteXmlTypes = ThirdParty.Types.ThatDeriveFromAny( + ThirdParty.Types.WithFullName("System.Xml.XmlNode")) + +from m in Application.Methods.WithReturnTypeIn(concreteXmlTypes) +select new { + m, + m.ReturnType, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns about method whose return type is +// **System.Xml.XmlNode** or any type derived from *XmlNode*. +// +// *XmlNode* implements the interface **System.Xml.Xpath.IXPathNavigable**. +// In most situation, returning this interface instead of the concrete +// type is a better *design* choice that will abstract client code +// from implementation details. +// + +// +// To fix a violation of this rule, change the concrete returned type +// to the suggested interface *IXPathNavigable* and refactor clients +// code if possible. +//]]> + Types should not extend System.Xml.XmlDocument +// ND2601:TypesShouldNotExtendSystemXmlXmlDocument + +warnif count > 0 from t in Application.Types where + t.DeriveFrom("System.Xml.XmlDocument".AllowNoMatch()) +select new { + t, + Debt = 20.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns aboud subclasses of **System.Xml.XmlDocument**. +// +// Do not create a subclass of *XmlDocument* if you want to +// create an XML view of an underlying object model or data source. +// + +// +// Instead of subclassing *XmlDocument*, you can use the interface +// **System.Xml.XPath.IXPathNavigable** implemented by the class +// *XmlDocument*. +// +// An alternative of using *XmlDocument*, is to use +// **System.Xml.Linq.XDocument**, aka **LINQ2XML**. +// More information on this can be found here: +// http://stackoverflow.com/questions/1542073/xdocument-or-xmldocument +//]]> + + + Float and Date Parsing must be culture aware +// ND2700:FloatAndDateParsingMustBeCultureAware + +warnif count > 0 + +let cultureUnawareMethods = + (from m in ThirdParty.Types.WithFullNameIn( + "System.DateTime", + "System.Single", + "System.Double", + "System.Decimal").ChildMethods() + where m.NbParameters > 0 && + (m.SimpleName.EqualsAny( + "Parse", "TryParse", "ToString")) && + !m.Name.Contains("IFormatProvider") + select m).ToHashSetEx() + +from m in JustMyCode.Methods.UsingAny(cultureUnawareMethods) +let cultureUnawareMethodsCalled = m.MethodsCalled.Intersect(cultureUnawareMethods) +select new { + m, + shouldntCall = cultureUnawareMethodsCalled, + Debt = (5 + 3*cultureUnawareMethodsCalled.Count()).ToMinutes().ToDebt(), + Severity = 5*cultureUnawareMethodsCalled.Count().ToMinutes().ToAnnualInterest() +} + +// +// Globalization is the design and development of applications that support +// localized user interfaces and regional data for users in multiple cultures. +// +// This rule warns about the usage of *non-globalized overloads* of +// the methods **Parse()**, **TryParse()** and **ToString()**, +// of the types **DateTime**, **float**, **double** and **decimal**. +// This is the symptom that your application is *at least partially* +// not globalized. +// +// *Non-globalized overloads* of these methods are the overloads +// that don't take a parameter of type **IFormatProvider**. +// + +// +// Globalize your applicaton and make sure to use the globalized overloads +// of these methods. In the column **MethodsCallingMe** of this rule result +// are listed the methods of your application that call the +// *non-globalized overloads*. +// +// More information on **Creating Globally Aware Applications** here: +// https://msdn.microsoft.com/en-us/library/cc853414(VS.95).aspx +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 5 minutes per application method calling at least one +// non-culture aware method called, plus 3 minutes per non-culture aware +// method called. +//]]> + + + Mark assemblies with assembly version +// ND2800:MarkAssembliesWithAssemblyVersion + +warnif count > 0 +let isUnityProject = ThirdParty.Assemblies.Any(a => a.Name.StartsWith("UnityEngine")) +from a in Application.Assemblies where + !isUnityProject && // Version is not required on Unity assemblies. + // You can still use the AssemblyVersionAttribute in Unity context + // as explained here https://forum.unity.com/threads/assemblyinfo-cs-solutions.529418/ + !a.HasAttribute ("System.Reflection.AssemblyVersionAttribute".AllowNoMatch()) && + // Make sure the assembly version is not defined through a way different than AssemblyVersionAttribute + (a.Version == null || a.Version.ToString() == "0.0.0.0") +select new { + a, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// The identity of an assembly is composed of the following information: +// +// • Assembly name +// +// • Version number +// +// • Culture +// +// • Public key (for strong-named assemblies). +// +// The .NET Framework uses the version number to uniquely identify an +// assembly, and to bind to types in strong-named assemblies. The +// version number is used together with version and publisher policy. +// By default, applications run only with the assembly version with +// which they were built. +// +// This rule matches assemblies that are not tagged with +// **System.Reflection.AssemblyVersionAttribute**. +// + +// +// To fix a violation of this rule, add a version number to the assembly +// by using the *System.Reflection.AssemblyVersionAttribute* attribute. +//]]> + Assemblies should have the same version +// ND2801:AssembliesShouldHaveTheSameVersion + +warnif count > 0 +let versionsLookup = Application.Assemblies.ToLookup(a => a.Version, a=> a) +let mostRepresentedVersion = versionsLookup.OrderByDescending(v => v.Count()).First().Key +from v in versionsLookup +where v.Key != mostRepresentedVersion +from a in v.ToArray() +select new { + a , + version = v.Key, + mostRepresentedVersion, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule reports application assemblies that have a version different +// than the version shared by most of application assemblies. +// +// Before fixing these issues, double check if there is a valid reason +// for dealing with more than one assembly version number. +// Typically this happens when the analyzed code base is made of assemblies +// that are not *compiled, developed or deployed* together. +// + +// +// If all assemblies of your application should have the same version number, +// just use the attribute **System.Reflection.AssemblyVersion** in a source +// file shared by the assemblies. +// +// Typically this source file is generated by a dedicated *MSBuild* task +// like this one http://www.msbuildextensionpack.com/help/4.0.5.0/html/d6c3b5e8-00d4-c826-1a73-3cfe637f3827.htm. +// +// Here you can find interesting assemblies versioning advices. +// http://stackoverflow.com/a/3905443/27194 +// +// By default issues of this rule have a severity set to **major** since +// unproper assemblies versioning can lead to complicated deployment problem. +//]]> + + + Public methods returning a reference needs a contract to ensure that a non-null reference is returned +// ND2900:PublicMethodsReturningAReferenceNeedsAContractToEnsureThatANonNullReferenceIsReturned + +warnif count > 0 +let ensureMethods = Application.Methods.WithFullName( + "System.Diagnostics.Contracts.__ContractsRuntime.Ensures(Boolean,String,String)") + +from ensureMethod in ensureMethods +from m in ensureMethod.ParentAssembly.ChildMethods where + m.IsPubliclyVisible && + !m.IsAbstract && + m.ReturnType != null && + // Identify that the return type is a reference type + (m.ReturnType.IsClass || m.ReturnType.IsInterface) && + !m.IsUsing(ensureMethod) && + + // Don't match method not implemented yet! + !m.CreateA("System.NotImplementedException".AllowNoMatch()) + +select new { + m, + ReturnTypeReference = m.ReturnType, + Debt = (5+3*m.MethodsCallingMe.Count()).ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// **Code Contracts** are useful to decrease ambiguity between callers and callees. +// Not ensuring that a reference returned by a method is *non-null* leaves ambiguity +// for the caller. This rule matches methods returning an instance of a reference type +// (class or interface) that doesn't use a **Contract.Ensure()** method. +// +// *Contract.Ensure()* is defined in the **Microsoft Code Contracts for .NET** +// library, and is typically used to write a code contract on returned reference: +// *Contract.Ensures(Contract.Result() != null, "returned reference is not null");* +// https://visualstudiogallery.msdn.microsoft.com/1ec7db13-3363-46c9-851f-1ce455f66970 +// + +// +// Use *Microsoft Code Contracts for .NET* on the public surface of your API, +// to remove most ambiguity presented to your client. Most of such ambiguities +// are about *null* or *not null* references. +// +// Don't use *null* reference if you need to define a method that might not +// return a result. Use instead the **TryXXX()** pattern exposed for example +// in the *System.Int32.TryParse()* method. +// +// The estimated Debt, which means the effort to fix such issue, is equal +// to 5 minutes per public application method that might return a null reference +// plus 3 minutes per method calling such method. +//]]> + + + + + Classes tagged with InitializeOnLoad should have a static constructor +// ND3200:ClassesTaggedWithInitializeOnLoadShouldHaveAStaticConstructor +warnif count > 0 +from t in Application.Types where t.HasAttribute("UnityEditor.InitializeOnLoadAttribute".AllowNoMatch()) +where t.Methods.SingleOrDefault(m => m.IsClassConstructor) == null +select new { + t, + Debt = 8.ToMinutes().ToDebt(), + Severity = Severity.Medium + } + +// +// The attribute *InitializeOnLoadAttribute* is used to initialize an Editor class +// when Unity loads and when your scripts are recompiled. +// +// The static constructors of the class tagged with this attribute +// is responsible for the initialization. +// + +// +// Create a static constructor for the matched classes. +//]]> + Avoid using non-generic GetComponent +// ND3201:AvoidUsingNonGenericGetComponent +warnif count > 0 +from m in Methods where m.IsUsing("UnityEngine.Component.GetComponent(Type)".AllowNoMatch()) +select new { + m, + Debt = 8.ToMinutes().ToDebt(), + Severity = Severity.Medium + } + +// +// Prefer using the generic version of *Component.GetComponent()* +// for type safety. +// + +// +// Refactor *GetComponent(typeof(MyType))* calls in *GetComponent()*. +//]]> + Avoid empty Unity message +// ND3202:AvoidEmptyUnityMessage +warnif count > 0 +from t in Types +where t.DeriveFrom("UnityEngine.MonoBehaviour".AllowNoMatch()) +from m in t.InstanceMethods where + !m.IsConstructor && + !m.IsPropertyGetter && + !m.IsPropertySetter && + m.NbLinesOfCode == 0 && + m.SimpleName.EqualsAny("Start","Update","FixedUpdate","LateUpdate", + "OnGUI","OnDisable","OnEnable") +select new { + m, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Low + } + +// +// Unity messages are called by the runtime even if they don't contain any code. +// + +// +// Remove matched methods to avoid unnecessary processing. +//]]> + Avoid using Time.fixedDeltaTime with Update +// ND3203:AvoidUsingTimeFixedDeltaTimeWithUpdate +warnif count > 0 +from m in Methods where + m.SimpleName.EqualsAny("Update","FixedUpdate") && + m.NbParameters == 0 && + m.IsUsing("UnityEngine.Time.get_fixedDeltaTime()".AllowNoMatch()) && + m.ParentType.DeriveFrom("UnityEngine.MonoBehaviour".AllowNoMatch()) +select new { + m, + Debt = 8.ToMinutes().ToDebt(), + Severity = Severity.Medium + } + +// +// *Update()* and *FixedUpdate()* are dependent on the frame rate. +// For reading the delta time it is recommended +// to use *Time.deltaTime* instead of *Time.fixedDeltaTime* +// because it automatically returns the right delta time +// if you are inside these functions. +// + +// +// Refactor calls to *Time.fixedDeltaTime* with *Time.deltaTime* +// within *Update()* and *FixedUpdate()* functions +//]]> + Use CreateInstance to create a scriptable object +// ND3204:UseCreateInstanceToCreateAScriptableObject + +warnif count > 0 +let scriptableObjects = Types.Where(t => t.DeriveFrom("UnityEngine.ScriptableObject".AllowNoMatch())) +let ctors = scriptableObjects.SelectMany(t => t.Constructors).ToHashSetEx() + +from m in Application.Methods.ThatCreateAny(scriptableObjects) +let ctorsCalled = m.MethodsCalled.Intersect(ctors).ToArray() +let typesInstantiated = ctorsCalled.ParentTypes() +where typesInstantiated.Any() +select new { + m, + shouldntInstantiate = typesInstantiated, + Debt = (5 + 5*typesInstantiated.Count()).ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// If *Foo* is a type that derives from *UnityEngine.ScriptableObject* +// the method *ScriptableObject.CreateInstance()* must be used +// to create an instance of *Foo*. +// +// This is because the Unity egine needs to create the scriptable object +// itself to handle Unity message methods. +// + +// +// Replace *new Foo()* expressions with +// *ScriptableObject.CreateInstance()* expressions. +//]]> + The SerializeField attribute is redundant on public fields +// ND3205:TheSerializeFieldAttributeIsRedundantOnPublicFields + +warnif count > 0 +from f in Fields where + f.IsPubliclyVisible && + f.HasAttribute("UnityEngine.SerializeField".AllowNoMatch()) +select new { + f, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// The *SerializeField* attribute makes a field display in the Inspector +// and causes it to be saved. +// +// Declaring a field as public makes a field display in the Inspector +// and causes it to be saved, as well as letting the field be +// publicly accessed by other scripts. +// +// Hence the *SerializeField* attribute is redundant for public fields. +// + +// +// Remove the *SerializeField* attribute on matched public fields. +//]]> + InitializeOnLoadMethod should tag only static and parameterless methods +// ND3206:InitializeOnLoadMethodShouldTagOnlyStaticAndParameterlessMethods +warnif count > 0 +from m in Application.Methods where + (m.HasAttribute ("UnityEditor.InitializeOnLoadMethodAttribute".AllowNoMatch()) || + m.HasAttribute ("UnityEditor.RuntimeInitializeOnLoadMethodAttribute".AllowNoMatch())) && + (m.NbParameters > 0 || !m.IsStatic) +select new { + m, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Medium + } + +// +// The attribute *InitializeOnLoadMethodAttribute* +// allows an editor static method to be initialized +// when Unity loads without action from the user. +// +// The attribute *RuntimeInitializeOnLoadMethodAttribute* +// has same behavior except that the methods are invoked +// after the game has been loaded. +// +// Both attributes can only tag static and parameterless +// methods. +// + +// +// Make the matched methods static and parameterless. +//]]> + Prefer using SetPixels32() over SetPixels() +// ND3207:PreferUsingSetPixels32OverSetPixels +warnif count > 0 + +let setPixelMethods = Types.WithFullName("UnityEngine.Texture2D") + .ChildMethods().WithSimpleNameIn("SetPixel","SetPixels").ToHashSetEx() + +from m in Application.Methods.UsingAny(setPixelMethods) +let shouldntCall = m.MethodsCalled.Intersect(setPixelMethods) + +select new { + m, + shouldntCall, + Debt = 15.ToMinutes().ToDebt(), + Severity = Severity.Major + } + +// +// The structure *Color* uses floating point values +// within the range [0.0,1.0] to represent each color component. +// +// The structure *Color32* uses a byte value +// within the range [0,255] to represent each color component. +// +// Hence *Color32* consumes 4 times less memory +// and is much faster to work with. +// + +// +// If 32bit-RGBA is compatible with your scenario, use +// *SetPixels32()* instead. +//]]> + Don't use System.Reflection in performance critical messages +// ND3208:DontUseSystemReflectionInPerformanceCriticalMessages + +warnif count > 0 + +let reflectionMembers = Namespaces.WithName("System.Reflection").ChildMembers() + +from m in Application.Methods.UsingAny(reflectionMembers) where + m.ParentType.DeriveFrom("UnityEngine.MonoBehaviour".AllowNoMatch()) && + m.SimpleName.EqualsAny("Update", "FixedUpdate", "LateUpdate", "OnGUI") + +let shouldntUsed = m.MembersUsed.Intersect(reflectionMembers) +select new { + m, + shouldntUsed, + Debt = (5 + 5*shouldntUsed.Count()).ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// *System.Reflection* is a slow API. +// +// Don't use it in performance critical messages like +// *Update*, *FixedUpdate*, *LateUpdate*, or *OnGUI*. +// + +// +// Don't use reflection in matched methods. +//]]> + + + + Discard generated Assemblies from JustMyCode +// ND3000:DiscardGeneratedAssembliesFromJustMyCode + +notmycode +from a in Application.Assemblies where + + // Assemblies generated for Xsl IL compilation for example are tagged with this attribute + a.HasAttribute ("System.CodeDom.Compiler.GeneratedCodeAttribute".AllowNoMatch()) + + // Consider '.Views.dll' ASP.NET Core Razor assemblies as notmycode + // because they are generated by the compiler +|| a.SimpleName.ToLower().EndsWith(".views") && + a.AssembliesUsed.Any(aUsed => aUsed.SimpleName.ToLower().EqualsAny( + "microsoft.aspnetcore.razor", "microsoft.aspnetcore.mvc.razor")) + +select a + +// +// This code query is prefixed with **notmycode**. +// This means that all application assemblies matched by this +// code query are removed from the *code base view* **JustMyCode.Assemblies**. +// It also means that all *namespaces*, *types*, *methods* and +// *fields* contained in a matched assembly are removed from +// the code base view *JustMyCode*. +// The code base view *JustMyCode* is used by most default code queries +// and rules. +// +// So far this query only matches application assemblies tagged +// with *System.CodeDom.Compiler.GeneratedCodeAttribute* and +// also ASP.NET Core Razor assemblies suffixed with *.Views.dll*. +// Make sure to make this query richer to discard your generated +// assemblies from the NDepend rules results. +// +// *notmycode* queries are executed before running others +// queries and rules. Also modifying a *notmycode* query +// provokes re-run of queries and rules that rely +// on the *JustMyCode* code base view. +// +// Several *notmycode* queries can be written to match *assemblies*, +// in which case this results in cumulative effect. +// +// Online documentation: +// https://www.ndepend.com/docs/cqlinq-syntax#NotMyCode +//]]> + Discard generated Namespaces from JustMyCode +// ND3001:DiscardGeneratedNamespacesFromJustMyCode + +notmycode + +// First gather assemblies written with VB.NET +let vbnetAssemblies = Application.Assemblies.Where( + a => a.SourceDecls.Any(decl => decl.SourceFile.FileNameExtension.ToLower() == ".vb")) +// Then find the My namespace and its child namespaces. +let vbnetMyNamespaces = vbnetAssemblies.ChildNamespaces().Where( + n => n.SimpleName == "My" || + n.ParentNamespaces.Any(nParent => nParent.SimpleName == "My")) + + +let generatedNamespace = Application.Namespaces.WithFullNameIn( + // COM/Import mshtml namespace and all its types are not-my-code + "mshtml", + // Some VB.NET generated code goes into this namespace + "Microsoft.VisualBasic", + // Roslyn can generate types in these namespaces, consider them as not-my-code + "Microsoft.CodeAnalysis", + "System.Runtime.CompilerServices" + +).Concat(Application.Namespaces.WithSimpleNameIn( + // Don't match types generated by the Refit infrastructure like the class PreserveAttribute + "RefitInternalGenerated")) + +// Discard types generated in Microsoft.Office.* namespaces. +// Issues won't be reported on this code. Especially issues related +// to the rule "Interface name should begin with a 'I'" broken +// by many interfaces in these namespaces. +let microsoftOffices = Application.Namespaces.Where(n => n.Name.StartsWith("Microsoft.Office")) + +from n in vbnetMyNamespaces.Concat(generatedNamespace).Concat(microsoftOffices) +select n + +// +// This code query is prefixed with **notmycode**. +// This means that all application namespaces matched by this +// code query are removed from the *code base view* **JustMyCode.Namespaces**. +// It also means that all *types*, *methods* and *fields* contained in a +// matched namespace are removed from the code base view *JustMyCode*. +// The code base view *JustMyCode* is used by most default code queries +// and rules. +// +// So far this query matches the **My** namespaces generated +// by the VB.NET compiler. This query also matches namespaces named +// **mshtml** or **Microsoft.Office.*** that are generated by tools. +// +// *notmycode* queries are executed before running others +// queries and rules. Also modifying a *notmycode* query +// provokes re-run of queries and rules that rely +// on the *JustMyCode* code base view. +// +// Several *notmycode* queries can be written to match *namespaces*, +// in which case this results in cumulative effect. +// +// Online documentation: +// https://www.ndepend.com/docs/cqlinq-syntax#NotMyCode +//]]> + Discard generated Types from JustMyCode +// ND3002:DiscardGeneratedTypesFromJustMyCode + +notmycode + +// Define some sets to quickly test EntityFramework generated types +let efContexts = Application.Types.Where(t => + t.DeriveFrom("System.Data.Entity.DbContext".AllowNoMatch()) && + t.SourceDecls.Count(sd => sd.SourceFile.FileName.ToLower().EndsWithAny(".context.cs", ".context.vb")) == 1).ToHashSetEx() +let efEntities = Application.Types.UsedByAny(efContexts).ToHashSetEx() +let efMigrations = Application.Types.Where(t => + t.DeriveFrom("System.Data.Entity.Migrations.DbMigration".AllowNoMatch()) || + t.DeriveFrom("Microsoft.EntityFrameworkCore.Migrations.Migration".AllowNoMatch())).ToHashSetEx() +let efModelSnapshots = Application.Types.Where(t => t.DeriveFrom("Microsoft.EntityFrameworkCore.Infrastructure.ModelSnapshot".AllowNoMatch())).ToHashSetEx() + +from t in Application.Types where + + // Don't consider anonymous types as JustMyCode + // C# and VB.NET anonymous types generated by the compiler satisfies these conditions + (t.IsGeneratedByCompiler && + t.ParentNamespace.Name.Length == 0 && + t.SimpleNameLike("AnonymousType")) || + + // Resources, Settings, or typed DataSet generated types for example, are tagged with this attribute + t.HasAttribute ("System.CodeDom.Compiler.GeneratedCodeAttribute".AllowNoMatch()) || + + // This attribute identifies a type or member that is not part of the user code for an application. + t.HasAttribute ("System.Diagnostics.DebuggerNonUserCodeAttribute".AllowNoMatch()) || + + // This attribute identifies a type that is defined with COM, elsewhere, so consider it not-my-code. + t.HasAttribute ("System.Runtime.InteropServices.GuidAttribute".AllowNoMatch()) || + + // Delegate types are always generated + t.IsDelegate || + + // Discard ASP.NET page types generated by aspnet_compiler.exe + // See: https://www.ndepend.com/FAQ.aspx#ASPNET + t.ParentNamespace.Name.EqualsAny("ASP", "__ASP") || + + // Discard ASP.NET special types + (t.SimpleName.EqualsAny("Startup","BundleConfig","RouteConfig") && + ThirdParty.Assemblies.WithNameWildcardMatchIn("System.Web*", "Microsoft.AspNetCore*").Any()) || + + // Discard Blazor and Razor _Imports types + (t.SimpleName == "_Imports" && + ThirdParty.Assemblies.WithNameWildcardMatchIn("*RazorPages*").Any()) || + + // Discard Blazor TypeInference types + (t.SimpleName == "TypeInference" && + ThirdParty.Assemblies.WithNameWildcardMatchIn("*RazorPages*").Any()) || + + // Discard DataSet classes and their nested types + (t.DeriveFrom("System.Data.DataSet".AllowNoMatch()) && + t.HasAttribute("System.ComponentModel.DesignerCategoryAttribute".AllowNoMatch())) || + (t.IsNested && t.ParentType != null && + t.ParentType.DeriveFrom("System.Data.DataSet".AllowNoMatch()) && + t.ParentType.HasAttribute("System.ComponentModel.DesignerCategoryAttribute".AllowNoMatch())) || + + // Discard DataSet TableAdapterManager classes their nested types + (t.SimpleName == "TableAdapterManager" && + t.DeriveFrom("System.ComponentModel.Component".AllowNoMatch())) || + (t.IsNested && t.ParentType != null && + t.ParentType.SimpleName == "TableAdapterManager" && + t.ParentType.DeriveFrom("System.ComponentModel.Component".AllowNoMatch())) || + + // Discard Xamarin form generated types that contain the method LoadDataTemplate() + (t.IsNested && + t.SimpleName.StartsWith("") && + t.Methods.Count(m => m.SimpleName == "LoadDataTemplate") == 1 && + t.ParentAssembly.AssembliesUsed.Count(a => a.Name.StartsWithAny("Xamarin", "Mono.Android")) > 0) || + + // Discard Xamarin Resource types + (t.IsNested && + t.ParentType != null && + t.ParentType.Name == "Resource" && + t.ParentAssembly.AssembliesUsed.Count(a => a.Name.StartsWithAny("Xamarin", "Mono.Android")) > 0) || + + // Discard Entity Framework generated DB context types and entities types used by DB context types! + efContexts.Contains(t) || + efEntities.Contains(t) || + efMigrations.Contains(t) || + efModelSnapshots.Contains(t) || + + // Discard types generated for code contract + t.FullName.StartsWith("System.Diagnostics.Contracts.__ContractsRuntime") || + t.FullName == "System.Diagnostics.Contracts.RuntimeContractsAttribute" || + + // Discard all types declared in a folder path containing the word "generated" + (t.SourceFileDeclAvailable && + t.SourceDecls.All(s => s.SourceFile.FilePath.ParentDirectoryPath.ToString().ToLower().Contains("generated"))) || + + // Types created by the test infrastructure + t.FullName == "AutoGeneratedProgram" || + + // Discard special types generated by the Roslyn compiler that are typically used by several methods + // and as a consequence, cannot be merged in application code through the option + // NDepend Project Properties > Analysis > Merge Code Generated by Compiler into Application Code + (t.IsGeneratedByCompiler && t.IsNested && t.SimpleName.StartsWith("<")) + +select t + +// +// This code query is prefixed with **notmycode**. +// This means that all application types matched by this +// code query are removed from the *code base view* **JustMyCode.Types**. +// It also means that all *methods* and *fields* contained in a +// matched type are removed from the code base view *JustMyCode*. +// The code base view *JustMyCode* is used by most default code queries +// and rules. +// +// So far this query matches several well-identified generated +// types, like the ones tagged with *System.CodeDom.Compiler.GeneratedCodeAttribute*. +// Make sure to make this query richer to discard your generated +// types from the NDepend rules results. +// +// *notmycode* queries are executed before running others +// queries and rules. Also modifying a *notmycode* query +// provokes re-run of queries and rules that rely +// on the *JustMyCode* code base view. +// +// Several *notmycode* queries can be written to match *types*, +// in which case this results in cumulative effect. +// +// Online documentation: +// https://www.ndepend.com/docs/cqlinq-syntax#NotMyCode +//]]> + Discard generated and designer Methods from JustMyCode +// ND3003:DiscardGeneratedAndDesignerMethodsFromJustMyCode + +notmycode + +// +// First define source files paths to discard +// +from a in Application.Assemblies +where a.SourceFileDeclAvailable +let asmSourceFilesPaths = a.SourceDecls.Select(s => s.SourceFile.FilePath) + +let sourceFilesPathsToDiscard = ( + from filePath in asmSourceFilesPaths + let filePathLower= filePath.ToString().ToLower() + where + filePathLower.EndsWithAny( + ".g.cs", // Popular pattern to name generated files. + ".g.vb", + ".generated.cs", + ".generated.vb") || + filePathLower.EndsWithAny( + ".xaml", // notmycode WPF xaml code + ".designer.cs", // notmycode C# Windows Forms designer code + ".designer.vb") // notmycode VB.NET Windows Forms designer code + || + // notmycode methods in source files in a directory containing generated + filePathLower.Contains("generated") + select filePath +).ToHashSetEx() + +// +// Second: discard methods in sourceFilesPathsToDiscard +// + +from m in a.ChildMethods +where (m.SourceFileDeclAvailable && + sourceFilesPathsToDiscard.Contains(m.SourceDecls.First().SourceFile.FilePath)) || + // Generated methods might be tagged with this attribute + m.HasAttribute ("System.CodeDom.Compiler.GeneratedCodeAttribute".AllowNoMatch()) || + + // This attributes identifies a type or member that is not part of the user code for an application. + m.HasAttribute ("System.Diagnostics.DebuggerNonUserCodeAttribute".AllowNoMatch()) || + + // Event adder/remover methods generated by the compiler. + ((m.IsEventAdder || m.IsEventRemover) && !m.SourceFileDeclAvailable) || + + // Default/implicit constructor generated by the compiler on class and structures that don't have constructor + (m.IsConstructor && + m.NbParameters == 0 && + (m.IsPublic || (m.IsProtected && m.ParentType.IsAbstract)) && + !m.SourceFileDeclAvailable) || + + // Methods of C#9 record types are considered as generated methods + // when they don't have source file decl. + // Doing so avoids some false positive issues on these methods. + (m.ParentType.IsRecord && (!m.SourceFileDeclAvailable || m.IsGeneratedByCompiler || + m.IsConstructor || m.IsClassConstructor)) || + + // Razor BuildRenderTree() generated method + (m.IsProtected && m.IsVirtual && m.Name == "BuildRenderTree(RenderTreeBuilder)") + +select new { m, m.NbLinesOfCode, m.SourceFileDeclAvailable, m.IsGeneratedByCompiler } + +// +// This code query is prefixed with **notmycode**. +// This means that all application methods matched by this +// code query are removed from the *code base view* **JustMyCode.Methods**. +// The code base view *JustMyCode* is used by most default code queries +// and rules. +// +// So far this query matches several well-identified generated +// methods, like the ones tagged with *System.CodeDom.Compiler.GeneratedCodeAttribute*, +// or the ones declared in a source file suffixed with *.designer.cs*. +// Make sure to make this query richer to discard your generated +// methods from the NDepend rules results. +// +// *notmycode* queries are executed before running others +// queries and rules. Also modifying a *notmycode* query +// provokes re-run of queries and rules that rely +// on the *JustMyCode* code base view. +// +// Several *notmycode* queries can be written to match *methods*, +// in which case this results in cumulative effect. +// +// Online documentation: +// https://www.ndepend.com/docs/cqlinq-syntax#NotMyCode +//]]> + Discard generated Fields from JustMyCode +// ND3004:DiscardGeneratedFieldsFromJustMyCode + +notmycode + +// Define WindowsForm fields defined as fields assigned by the InitializeComponent() WindowsForm methods. +let winFormInitializeComponentsMethods = + Application.Methods.WithName("InitializeComponent()") + .Where(m => m.ParentType.BaseClass != null && m.ParentType.BaseClass.ParentNamespace.Name == "System.Windows.Forms") +let winFormFields = winFormInitializeComponentsMethods.SelectMany(m => m.FieldsAssigned).ToHashSetEx() + +from f in Application.Fields where + + // Discard WindowsForm fields + winFormFields.Contains(f) || + + // Eliminate "components" generated in Windows Form Control context + (f.Name == "components" && f.ParentType.DeriveFrom("System.Windows.Forms.Control".AllowNoMatch())) || + + // Eliminate tagHelper generated fields + f.Name == "_tagHelperStringValueBuffer" || + + // Eliminate XAML generated fields + // IComponentConnector is XAML specific and is automatically implemented for every Window, Page and UserControl. + f.ParentType.Implement( "System.Windows.Markup.IComponentConnector".AllowNoMatch()) || // WPF IComponentConnector + f.ParentType.Implement("Windows.UI.Xaml.Markup.IComponentConnector".AllowNoMatch()) || // UWP IComponentConnector + + f.HasAttribute ("System.CodeDom.Compiler.GeneratedCodeAttribute".AllowNoMatch()) || + + // Property backing fields generated by the compiler + (f.IsGeneratedByCompiler && !f.IsEventDelegateObject) || + + // Match fields generated by the ASP.NET infrastructure + // in System.Web.UI classes like Page, Control, MasterPage... + (f.FieldType != null && + f.ParentType.BaseClasses.Any(bc => bc.ParentNamespace.Name.StartsWith("System.Web.UI")) && + (f.FieldType.ParentNamespace.Name.StartsWith("System.Web.UI") || + f.FieldType.BaseClasses.Any(bc => bc.ParentNamespace.Name.StartsWith("System.Web.UI")) + )) || + + // Match fields named 'mappingSource' for DataContext classes + (f.Name == "mappingSource" && f.ParentType.DeriveFrom("System.Data.Linq.DataContext".AllowNoMatch())) + +select f + +// +// This code query is prefixed with **notmycode**. +// This means that all application fields matched by this +// code query are removed from the *code base view* **JustMyCode.Fields**. +// The code base view *JustMyCode* is used by most default code queries +// and rules. +// +// This query matches application fields tagged +// with *System.CodeDom.Compiler.GeneratedCodeAttribute*, and +// *Windows Form*, *WPF* and *UWP* fields generated by the designer. +// Make sure to make this query richer to discard your generated +// fields from the NDepend rules results. +// +// *notmycode* queries are executed before running others +// queries and rules. Also modifying a *notmycode* query +// provokes re-run of queries and rules that rely +// on the *JustMyCode* code base view. +// +// Several *notmycode* queries can be written to match *fields*, +// in which case this results in cumulative effect. +// +// Online documentation: +// https://www.ndepend.com/docs/cqlinq-syntax#NotMyCode +//]]> + JustMyCode code elements +from elem in JustMyCode.CodeElements +select new { + elem, + loc = elem.IsCodeContainer ? elem.AsCodeContainer.NbLinesOfCode : null +} + +// +// This code query enumerates all +// *assemblies*, *namespaces*, *types*, *methods* and *fields* +// in your application, that are considered as being your code. +// +// This means concretely that the *ICodeBaseView* **JustMyCode** +// only shows these code elements. This code base view is used by +// many default code rule to avoid being warned on code elements +// that you don't consider as your code - typically the code +// elements generated by a tool. +// +// These code elements are the ones that are not matched +// by any quere prefixed with **notmycode**. +//]]> + NotMyCode code elements +from elem in Application.CodeElements.Where(element => !JustMyCode.Contains(element)) +select new { + elem, + loc = elem.IsCodeContainer ? elem.AsCodeContainer.NbLinesOfCode : null +} + +// +// This code query enumerates all +// *assemblies*, *namespaces*, *types*, *methods* and *fields* +// in your application, that are considered as not being your code. +// +// This means concretely that the *ICodeBaseView* **JustMyCode** +// hide these code elements. This code base view is used by +// many default code rules to avoid being warned on code elements +// that you don't consider as your code - typically the code +// elements generated by a tool. +// +// These code elements are the ones matched by queries prefixed with +// **notmycode**. +//]]> + + + + +from issue in Issues +where issue.WasAdded() +select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity }]]> + +from issue in Issues +where issue.WasFixed() +select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity }]]> + +from issue in Issues +where !issue.WasAdded() && + (issue.DebtDiff() > Debt.Zero || issue.AnnualInterestDiff() > AnnualInterest.Zero) +select new { + issue, + issue.Debt, debtDiff = issue.DebtDiff(), + issue.AnnualInterest, annualInterestDiff = issue.AnnualInterestDiff(), + issue.Severity +} + +// +// An issue is considered worsened if its *debt* increased since the baseline. +// +// Debt documentation: https://www.ndepend.com/docs/technical-debt#Debt +// +]]> + +from issue in Issues +where issue.Severity == Severity.Blocker +select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity } + +// +// An issue with the severity **Blocker** cannot move to production, it must be fixed. +// +// The severity of an issue is inferred from the issue *annual interest* +// and thresholds defined in the NDepend Project Properties > Issue and Debt. +//]]> + +from issue in Issues +where issue.Severity == Severity.Critical +select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity } + +// +// An issue with a severity level **Critical** shouldn't move to production. +// It still can for business imperative needs purposes, but at worth it must be fixed during the next iterations. +// +// The severity of an issue is inferred from the issue *annual interest* +// and thresholds defined in the NDepend Project Properties > Issue and Debt. +//]]> + +from issue in Issues +where issue.Severity == Severity.High +select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity } + +// +// An issue with a severity level **High** should be fixed quickly, but can wait until the next scheduled interval. +// +// The severity of an issue is inferred from the issue *annual interest* +// and thresholds defined in the NDepend Project Properties > issue and Debt. +//]]> + +from issue in Issues +where issue.Severity == Severity.Medium +select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity } + +// +// An issue with a severity level **Medium** is a warning that if not fixed, won't have a significant impact on development. +// +// The severity of an issue is inferred from the issue *annual interest* +// and thresholds defined in the NDepend Project Properties > issue and Debt. +//]]> + +from issue in Issues +where issue.Severity == Severity.Low +select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity } + +// +// The severity level **Low** is used by issues that have a zero, or close to zero, +// value for **Annual Interest**. +// +// Issues with a **Low** or **Medium** severity level represents small improvements, +// ways to make the code looks more elegant. +// +// The **Broken Window Theory** https://en.wikipedia.org/wiki/Broken_windows_theory states that: +// +// *"Consider a building with a few broken windows. +// If the windows are not repaired, the tendency is for vandals to break a few more windows. +// Eventually, they may even break into the building, and if it's unoccupied, perhaps become +// squatters or light fires inside."* +// + +// Issues with a *Low* or *Medium* severity level represents the *broken windows* of a code base. +// If they are not fixed, the tendency is for developers to not care for living +// in an elegant code, which will result in extra-maintenance-cost in the long term. +// +// The severity of an issue is inferred from the issue *annual interest* +// and thresholds defined in the NDepend Project Properties > issue and Debt. +//]]> + +from issue in Issues +where issue.Severity.EqualsAny(Severity.Blocker, Severity.Critical, Severity.High) +select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity } + +// +// The number of issues with a severity Blocker, Critical or High. +// +// An issue with the severity **Blocker** cannot move to production, it must be fixed. +// +// An issue with a severity level **Critical** shouldn't move to production. +// It still can for business imperative needs purposes, but at worth it must be fixed during the next iterations. +// +// An issue with a severity level **High** should be fixed quickly, but can wait until the next scheduled interval. +//]]> + +from issue in Issues +select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity } + +// +// The number of issues no matter the issue severity. +//]]> + +from issue in context.IssuesSet.AllSuppressedIssues +select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity } + +// +// The number of issues suppressed with the usage of SuppressMessage. +// See the suppressed issues documentation here: +// https://www.ndepend.com/docs/suppress-issues +//]]> + + + +from rule in Rules +select new { + rule, + issues = rule.Issues(), + debt = rule.Debt(), + annualInterest = rule.AnnualInterest(), + maxSeverity = rule.IsViolated() && rule.Issues().Any() ? + (Severity?)rule.Issues().Max(i => i.Severity) : null +} + +// +// This trend metric counts the number of active rules. +// This count includes violated and not violated rules. +// This count includes critical and non critical rules. +// +// When no baseline is available, rules that rely on diff are not counted. +// If you observe that this count slightly decreases with no apparent reason, +// the reason is certainly that rules that rely on diff are not counted +// because the baseline is not defined. +//]]> + +from rule in Rules +where rule.IsViolated() +select new { + rule, + issues = rule.Issues(), + debt = rule.Debt(), + annualInterest = rule.AnnualInterest(), + maxSeverity = rule.IsViolated() && rule.Issues().Any() ? + (Severity?)rule.Issues().Max(i => i.Severity) : null +} + +// +// This trend metric counts the number of active rules that are violated. +// This count includes critical and non critical rules. +// +// When no baseline is available, rules that rely on diff are not counted. +// If you observe that this count slightly decreases with no apparent reason, +// the reason is certainly that rules that rely on diff are not counted +// because the baseline is not defined. +//]]> + +from rule in Rules +where rule.IsViolated() && rule.IsCritical +select new { + rule, + issues = rule.Issues(), + debt = rule.Debt(), + annualInterest = rule.AnnualInterest(), + maxSeverity = rule.IsViolated() && rule.Issues().Any() ? + (Severity?)rule.Issues().Max(i => i.Severity) : null +} + +// +// This trend metric counts the number of critical active rules that are violated. +// +// The concept of critical rule is useful to pinpoint certain rules that should not be violated. +// +// When no baseline is available, rules that rely on diff are not counted. +// If you observe that this count slightly decreases with no apparent reason, +// the reason is certainly that rules that rely on diff are not counted +// because the baseline is not defined. +//]]> + + + +from qualityGate in QualityGates +select new { + qualityGate , + qualityGate.ValueString, + qualityGate.Status, +} + +// +// This trend metric counts the number of active quality gates, +// no matter the gate status (Pass, Warn, Fail). +// +// When no baseline is available, quality gates that rely on diff are not counted. +// If you observe that this count slightly decreases with no apparent reason, +// the reason is certainly that quality gates that rely on diff are not counted +// because the baseline is not defined. +//]]> + +from qualityGate in QualityGates +where qualityGate.Warn +select new { + qualityGate , + qualityGate.ValueString, +} + +// +// This trend metric counts the number of active quality gates that warns. +// +// When no baseline is available, quality gates that rely on diff are not counted. +// If you observe that this count slightly decreases with no apparent reason, +// the reason is certainly that quality gates that rely on diff are not counted +// because the baseline is not defined. +//]]> + +from qualityGate in QualityGates +where qualityGate.Fail +select new { + qualityGate , + qualityGate.ValueString, +} + +// +// This trend metric counts the number of active quality gates that fails. +// +// When no baseline is available, quality gates that rely on diff are not counted. +// If you observe that this count slightly decreases with no apparent reason, +// the reason is certainly that quality gates that rely on diff are not counted +// because the baseline is not defined. +//]]> + + + +let timeToDev = codeBase.EffortToDevelop() +let debt = Issues.Sum(i => i.Debt) +select 100d * debt.ToManDay() / timeToDev.ToManDay() + +// +// This Trend Metric name is suffixed with (Metric) +// to avoid query name collision with the Quality Gate with same name. +// +// Infer a percentage from: +// +// • the estimated total time to develop the code base +// +// • and the the estimated total time to fix all issues (the Debt). +// +// Estimated total time to develop the code base is inferred from +// # lines of code of the code base and from the +// *Estimated number of man-day to develop 1000 logicial lines of code* +// setting found in NDepend Project Properties > Issue and Debt. +// +// Debt documentation: https://www.ndepend.com/docs/technical-debt#Debt +// ]]> + +Issues.Sum(i => i.Debt).ToManDay() + +// +// This Trend Metric name is suffixed with (Metric) +// to avoid query name collision with the Quality Gate with same name. +// +// Debt documentation: https://www.ndepend.com/docs/technical-debt#Debt +//]]> + +let debt = Issues.Sum(i => i.Debt) +let debtInBaseline = IssuesInBaseline.Sum(i => i.Debt) +select (debt - debtInBaseline).ToManDay() + +// +// This Trend Metric name is suffixed with (Metric) +// to avoid query name collision with the Quality Gate with same name. +// +// Debt added (or fixed if negative) since baseline. +// +// Debt documentation: https://www.ndepend.com/docs/technical-debt#Debt +//]]> + +Issues.Sum(i => i.AnnualInterest).ToManDay() + +// +// This Trend Metric name is suffixed with (Metric) +// to avoid query name collision with the Quality Gate with same name. +// +// Annual Interest documentation: https://www.ndepend.com/docs/technical-debt#AnnualInterest +//]]> + +let ai = Issues.Sum(i => i.AnnualInterest) +let aiInBaseline = IssuesInBaseline.Sum(i => i.AnnualInterest) +select (ai - aiInBaseline).ToManDay() + +// +// This Trend Metric name is suffixed with (Metric) +// to avoid query name collision with the Quality Gate with same name. +// +// Annual Interest added (or fixed if negative) since baseline. +// +// Annual Interest documentation: https://www.ndepend.com/docs/technical-debt#AnnualInterest +//]]> + +(Issues.Sum(i =>i.Debt).BreakingPoint(Issues.Sum(i =>i.AnnualInterest))).TotalYears() + +// +// The **breaking point** of a set of issues is the **debt** divided by the **annual interest**. +// +// The *debt* is the estimated cost-to-fix the issues. +// +// The *annual interest* is the estimated cost-to-**not**-fix the issues, per year. +// +// Hence the *breaking point* is the point in time from now, when not fixing the issues cost as much as fixing the issue. +// +// Breaking Point documentation: https://www.ndepend.com/docs/technical-debt#BreakingPoint +// ]]> + +let issues = Issues.Where(i => i.Severity.EqualsAny(Severity.Blocker, Severity.Critical, Severity.High)) +select (issues.Sum(i =>i.Debt).BreakingPoint(issues.Sum(i =>i.AnnualInterest))).TotalYears() + +// +// The **breaking point** of a set of issues is the **debt** divided by the **annual interest**. +// +// The *debt* is the estimated cost-to-fix the issues. +// +// The *annual interest* is the estimated cost-to-**not**-fix the issues, per year. +// +// Hence the *breaking point* is the point in time from now, when not fixing the issues cost as much as fixing the issue. +// +// Breaking Point documentation: https://www.ndepend.com/docs/technical-debt#BreakingPoint +// ]]> + + + +codeBase.NbLinesOfCode]]> + +JustMyCode.Methods.Sum(m => m.NbLinesOfCode) + +// JustMyCode is defined by code queries prefixed with 'notmycode' +// in the group 'Defining JustMyCode'. +]]> + +Application.Methods.Except(JustMyCode.Methods).Sum(m => m.NbLinesOfCode) + +// JustMyCode is defined by code queries prefixed with 'notmycode' +// in the group 'Defining JustMyCode'. +]]> + +from a in Application.Assemblies +let nbLocAdded = !a.IsPresentInBothBuilds() + ? a.NbLinesOfCode + : (a.NbLinesOfCode != null && a.OlderVersion().NbLinesOfCode != null) + ? a.NbLinesOfCode - (int)a.OlderVersion().NbLinesOfCode + : 0 +select (double?)nbLocAdded + + +// A value is computed by this Trend Metric query +// only if a Baseline for Comparison is provided. +// See Project Properties > Analysis > Baseline for Comparison +]]> + +Application.Assemblies.SelectMany( + a => a.SourceDecls.Select(sd => sd.SourceFile.FilePathString.ToLower())) +.Distinct() +.Count() + +// +// This trend metric counts the number of source files. +// +// If a value 0 is obtained, it means that at analysis time, +// assemblies PDB files were not available. +// https://www.ndepend.com/docs/ndepend-analysis-inputs-explanation +// +// So far source files cannot be matched by a code query. +// However editing the query "Application Types" and then +// *Group by source file declarations* will list source files +// with types source declarations. +//]]> + +codeBase.NbILInstructions +]]> + +Application.Methods.Except(JustMyCode.Methods).Sum(m => m.NbILInstructions) + +// JustMyCode is defined by code queries prefixed with 'notmycode' +// in the group 'Defining JustMyCode'. +]]> + +codeBase.NbLinesOfComment + +// +// This trend metric returns the number of lines of comment +// counted in application source files. +// +// So far commenting information is only extracted from C# source code +// and VB.NET support is planned. +//]]> + +codeBase.PercentageComment + +// +// This trend metric returns the percentage of comment +// compared to the number of **logical**lines of code. +// +// So far commenting information is only extracted from C# source code +// and VB.NET support is planned. +//]]> + +from a in Application.Assemblies +select new { + a, + Debt = a.AllDebt(), + Issues = a.AllIssues(), + a.NbLinesOfCode +} + +// +// This trend metric query counts all application assemblies. +// For each assembly it shows the estimated **all** technical-debt and **all** issues. +// **All** means debt and issues of the assembly and of its child namespaces, types and members. +//]]> + +from n in Application.Namespaces +select new { + n, + Debt = n.AllDebt(), + Issues = n.AllIssues(), + n.NbLinesOfCode +} + +// +// This trend metric query counts all application namespaces. +// For each namespace it shows the estimated **all** technical-debt and **all** issues. +// **All** means debt and issues of the namespace and of its child types and members. +//]]> + +from t in Application.Types.Where(t => !t.IsGeneratedByCompiler) +select new { + t, + Debt = t.AllDebt(), + Issues = t.AllIssues(), + t.NbLinesOfCode +} + +// +// This trend metric query counts all application types non-generated by compiler. +// For each type it shows the estimated **all** technical-debt and **all** issues. +// **All** means debt and issues of the type and of its child members. +//]]> + +Application.Types.Where(t => t.IsPubliclyVisible && !t.IsGeneratedByCompiler)]]> + +Application.Types.Where(t => t.IsClass && !t.IsGeneratedByCompiler)]]> + +Application.Types.Where(t => t.IsClass && t.IsAbstract && !t.IsGeneratedByCompiler)]]> + +Application.Types.Where(t => t.IsInterface)]]> + +Application.Types.Where(t => t.IsStructure && !t.IsGeneratedByCompiler)]]> + +from m in Application.Methods.Where(m => !m.IsGeneratedByCompiler) +select new { + m, + Debt = m.Debt(), + Issues = m.Issues(), + m.NbLinesOfCode +} + +// +// This trend metric query counts all application methods non-generated by compiler. +// For each method it shows the estimated technical-debt and the issues. +//]]> + +Application.Methods.Where(m => m.IsAbstract)]]> + +Application.Methods.Where(m => !m.IsAbstract && !m.IsGeneratedByCompiler)]]> + +from f in Application.Fields.Where(f => + !f.IsEnumValue && + !f.ParentType.IsEnumeration && + !f.IsGeneratedByCompiler && + !f.IsLiteral) +select new { + f, + Debt = f.AllDebt(), + Issues = f.AllIssues() +} + +// +// This trend metric query counts all application fields non-generated by compiler +// that are not enumeration values nor constant values. +// For each field it shows the estimated technical-debt and the issues. +//]]> + + + +JustMyCode.Methods + .Max(m => m.NbLinesOfCode) + +// Here is the code query to get the (JustMyCode) method with largest # Lines of Code +// JustMyCode.Methods.OrderByDescending(m => m.NbLinesOfCode).Take(1).Select(m => new {m, m.NbLinesOfCode})]]> + +Application.Methods.Where(m => m.NbLinesOfCode > 0) + .Average(m => m.NbLinesOfCode)]]> + +Application.Methods.Where(m => m.NbLinesOfCode >= 3) + .Average(m => m.NbLinesOfCode)]]> + +JustMyCode.Types + .Max(t => t.NbLinesOfCode) + +// Here is the code query to get the (JustMyCode) type with largest # Lines of Code +// JustMyCode.Types.OrderByDescending(t => t.NbLinesOfCode).Take(1).Select(t => new {t, t.NbLinesOfCode})]]> + +Application.Types.Where(t => t.NbLinesOfCode > 0) + .Average(t => t.NbLinesOfCode)]]> + +Application.Methods + .Max(m => m.CyclomaticComplexity) + +// Here is the code query to get the most complex method, according to Cyclomatic Complexity +// Application.Methods.OrderByDescending(m => m.CyclomaticComplexity).Take(1).Select(m => new {m, m.CyclomaticComplexity})]]> + +Application.Methods.Where(m => m.NbLinesOfCode> 0) + .Average(m => m.CyclomaticComplexity)]]> + +Application.Methods + .Max(m => m.ILCyclomaticComplexity) + +// Here is the code query to get the most complex method, according to Cyclomatic Complexity computed from IL code. +// Application.Methods.OrderByDescending(m => m.ILCyclomaticComplexity).Take(1).Select(m => new {m, m.CyclomaticComplexity})]]> + +Application.Methods.Where(m => m.NbILInstructions> 0) + .Average(m => m.ILCyclomaticComplexity)]]> + +Application.Methods + .Max(m => m.ILNestingDepth) + +// Here is the code query to get the method with higher ILNestingDepth. +// Application.Methods.OrderByDescending(m => m.ILNestingDepth).Take(1).Select(m => new {m, m.ILNestingDepth})]]> + +Application.Methods.Where(m => m.NbILInstructions> 0) + .Average(m => m.ILNestingDepth)]]> + +Application.Types + .Max(t => t.NbMethods) + +// Here is the code query to get the (JustMyCode) type with largest # of Methods +// JustMyCode.Types.OrderByDescending(t => t.NbMethods).Take(1).Select(t => new {t, t.Methods})]]> + +Application.Types.Average(t => t.NbMethods)]]> + +Application.Types.Where(t => t.IsInterface) + .Max(t => t.NbMethods) + +// Here is the code query to get the (JustMyCode) type with largest # of Methods +// JustMyCode.Types.OrderByDescending(t => t.NbMethods).Take(1).Select(t => new {t, t.Methods})]]> + +JustMyCode.Types.Where(t => t.IsInterface) + .Average(t => t.NbMethods)]]> + + + +codeBase.PercentageCoverage]]> + +codeBase.NbLinesOfCodeCovered]]> + +codeBase.NbLinesOfCodeNotCovered]]> + +Application.Methods.Where(m => m.ExcludedFromCoverageStatistics).Sum(m => m.NbLinesOfCode).ToNullableDouble() + +// +// **Lines of Code Uncoverable** are lines of code in methods tagged with an *Uncoverable attribute* +// or methods in types or assemblies tagged with an *Uncoverable attribute*. +// +// These methods can be listed with the code query: +// *from m in Application.Methods where m.ExcludedFromCoverageStatistics select new { m, m.NbLinesOfCode }* +// +// Typically the attribute *System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute* +// is used as the *Uncoverable attribute*. +// +// An additional custom *Uncoverable attribute* can be defined in the: +// NDepend Project Properties > Analysis > Code Coverage > Un-Coverable attributes. +// +// If coverage is imported from VS coverage technologies those attributes are +// also considered as *Uncoverable attribute*: +// *System.Diagnostics.DebuggerNonUserCodeAttribute* +// *System.Diagnostics.DebuggerHiddenAttribute*. +// +// If coverage data imported at analysis time is not *in-sync* with the analyzed code base, +// this code query will also list methods not defined in the coverage data imported. +//]]> + +Application.Types.Where(t => t.PercentageCoverage == 100) + .Sum(t => t.NbLinesOfCodeCovered) + +// +// A line of code covered by tests is *even more valuable* if it is in a type 100% covered by test. +// +// Covering 90% of a class is not enough. +// +// • It means that this 10% uncovered code is hard-to-test, +// +// • which means that this code is not well-designed, +// +// • which means that it is error-prone. +// +// Better test error-prone code, isn't it? +//]]> + +Application.Methods.Where(m => m.PercentageCoverage == 100) + .Sum(m => m.NbLinesOfCodeCovered) + +// +// The same remark than in the Trend Metric **# Lines of Code in Types 100% Covered** +// applies for method 100% covered. +// +// A line of code covered by tests is *even more valuable* if it is in a method 100% covered by test. +//]]> + + +(from m in JustMyCode.Methods + +// Don't match too short methods +where m.NbLinesOfCode > 10 + +let CC = m.CyclomaticComplexity +let uncov = (100 - m.PercentageCoverage) / 100f +let CRAP = (CC * CC * uncov * uncov * uncov) + CC +where CRAP != null && CRAP > 30 select CRAP) +.Max(CRAP => CRAP) + +// +// **Change Risk Analyzer and Predictor** (i.e. CRAP) is a code metric +// that helps in pinpointing overly complex and untested code. +// Is has been first defined here: +// http://www.artima.com/weblogs/viewpost.jsp?thread=215899 +// +// The Formula is: **CRAP(m) = CC(m)^2 * (1 – cov(m)/100)^3 + CC(m)** +// +// • where *CC(m)* is the *cyclomatic complexity* of the method *m* +// +// • and *cov(m)* is the *percentage coverage* by tests of the method *m* +// +// Matched methods cumulates two highly *error prone* code smells: +// +// • A complex method, difficult to develop and maintain. +// +// • Non 100% covered code, difficult to refactor without any regression bug. +// +// The higher the CRAP score, the more painful to maintain and error prone is the method. +// +// An arbitrary threshold of 30 is fixed for this code rule as suggested by inventors. +// +// Notice that no amount of testing will keep methods with a Cyclomatic Complexity +// higher than 30, out of CRAP territory. +// +// Notice that CRAP score is not computed for too short methods +// with less than 10 lines of code. +// +// To list methods with higher C.R.A.P scores, please refer to the default rule: +// *Test and Code Coverage* > *C.R.A.P method code metric* +//]]> + + +(from m in JustMyCode.Methods + +// Don't match too short methods +where m.NbLinesOfCode > 10 + +let CC = m.CyclomaticComplexity +let uncov = (100 - m.PercentageCoverage) / 100f +let CRAP = (CC * CC * uncov * uncov * uncov) + CC +where CRAP != null && CRAP > 30 select CRAP) +.Average(CRAP => CRAP) + +// +// **Change Risk Analyzer and Predictor** (i.e. CRAP) is a code metric +// that helps in pinpointing overly complex and untested code. +// Is has been first defined here: +// http://www.artima.com/weblogs/viewpost.jsp?thread=215899 +// +// The Formula is: **CRAP(m) = CC(m)^2 * (1 – cov(m)/100)^3 + CC(m)** +// +// • where *CC(m)* is the *cyclomatic complexity* of the method *m* +// +// • and *cov(m)* is the *percentage coverage* by tests of the method *m* +// +// Matched methods cumulates two highly *error prone* code smells: +// +// • A complex method, difficult to develop and maintain. +// +// • Non 100% covered code, difficult to refactor without any regression bug. +// +// The higher the CRAP score, the more painful to maintain and error prone is the method. +// +// An arbitrary threshold of 30 is fixed for this code rule as suggested by inventors. +// +// Notice that no amount of testing will keep methods with a Cyclomatic Complexity +// higher than 30, out of CRAP territory. +// +// Notice that CRAP score is not computed for too short methods +// with less than 10 lines of code. +// +// To list methods with higher C.R.A.P scores, please refer to the default rule: +// *Test and Code Coverage* > *C.R.A.P method code metric* +//]]> + + + +from a in ThirdParty.Assemblies +select new { a, a.AssembliesUsingMe }]]> + +from n in ThirdParty.Namespaces +select new { n, n.NamespacesUsingMe }]]> + +from t in ThirdParty.Types +select new { t, t.TypesUsingMe }]]> + +from m in ThirdParty.Methods +select new { m, m.MethodsCallingMe }]]> + +from f in ThirdParty.Fields +where !f.ParentType.IsEnumeration +select new { f, f.MethodsUsingMe }]]> + +from elem in ThirdParty.CodeElements +where !(elem.IsField && elem.AsField.ParentType.IsEnumeration) +let users = elem.IsMethod ? elem.AsMethod.MethodsCallingMe.Cast() : + elem.IsField ? elem.AsField.MethodsUsingMe.Cast() : + elem.IsType ? elem.AsType.TypesUsingMe.Cast() : + elem.IsNamespace ? elem.AsNamespace.NamespacesUsingMe.Cast() : + elem.AsAssembly.AssembliesUsingMe.Cast() +select new { elem, users } +]]> + + + + New assemblies +from a in Application.Assemblies where a.WasAdded() +select new { a, a.NbLinesOfCode } + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *assemblies* that have been added since the *baseline*. +//]]> + Assemblies removed +from a in codeBase.OlderVersion().Application.Assemblies where a.WasRemoved() +select new { a, a.NbLinesOfCode } + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *assemblies* that have been removed since the *baseline*. +//]]> + Assemblies where code was changed +from a in Application.Assemblies where a.CodeWasChanged() +select new { + a, + a.NbLinesOfCode, + oldNbLinesOfCode = a.OlderVersion().NbLinesOfCode.GetValueOrDefault() , + delta = (int) a.NbLinesOfCode.GetValueOrDefault() - a.OlderVersion().NbLinesOfCode.GetValueOrDefault() +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *assemblies* in which, code has been changed since the *baseline*. +//]]> + New namespaces +from n in Application.Namespaces where + !n.ParentAssembly.WasAdded() && + n.WasAdded() +select new { n, n.NbLinesOfCode } + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *namespaces* that have been added since the *baseline*. +//]]> + Namespaces removed +from n in codeBase.OlderVersion().Application.Namespaces where + !n.ParentAssembly.WasRemoved() && + n.WasRemoved() +select new { n, n.NbLinesOfCode } + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *namespaces* that have been removed since the *baseline*. +//]]> + Namespaces where code was changed +from n in Application.Namespaces where n.CodeWasChanged() +select new { + n, + n.NbLinesOfCode, + oldNbLinesOfCode = n.OlderVersion().NbLinesOfCode.GetValueOrDefault() , + delta = (int) n.NbLinesOfCode.GetValueOrDefault() - n.OlderVersion().NbLinesOfCode.GetValueOrDefault() +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *namespaces* in which, code has been changed since the *baseline*. +//]]> + New types +from t in Application.Types where + !t.ParentNamespace.WasAdded() && + t.WasAdded() && + !t.IsGeneratedByCompiler +select new { t, t.NbLinesOfCode } + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *types* that have been added since the *baseline*. +//]]> + Types removed +from t in codeBase.OlderVersion().Application.Types where + !t.ParentNamespace.WasRemoved() && + t.WasRemoved() && + !t.IsGeneratedByCompiler +select new { t, t.NbLinesOfCode } + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *types* that have been removed since the *baseline*. +//]]> + Types where code was changed + +from t in Application.Types where t.CodeWasChanged() +//select new { t, t.NbLinesOfCode } +select new { + t, + t.NbLinesOfCode, + oldNbLinesOfCode = t.OlderVersion().NbLinesOfCode , + delta = (int?) t.NbLinesOfCode - t.OlderVersion().NbLinesOfCode +} +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *types* in which, code has been changed since the *baseline*. +// +// To visualize changes in code, right-click a matched type and select: +// +// • Compare older and newer versions of source file +// +// • Compare older and newer versions disassembled with Reflector +//]]> + Heuristic to find types moved from one namespace or assembly to another +let typesRemoved = codeBase.OlderVersion().Types.Where(t => t.WasRemoved()) +let typesAdded = Types.Where(t => t.WasAdded()) + +from tMoved in typesAdded.Join( + typesRemoved, + t => t.Name, + t => t.Name, + (tNewer, tOlder) => new { tNewer, + OlderParentNamespace = tOlder.ParentNamespace, + OlderParentAssembly = tOlder.ParentAssembly } ) +select tMoved + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *types* moved from one namespace or assembly to another. +// The heuristic implemented consists in making a **join LINQ query** on +// type name (without namespace prefix), applied to the two sets of types *added* +// and types *removed*. +//]]> + Types directly using one or several types changed +let typesChanged = Application.Types.Where(t => t.CodeWasChanged()).ToHashSetEx() + +from t in JustMyCode.Types.UsingAny(typesChanged) where + !t.CodeWasChanged() && + !t.WasAdded() +let typesChangedUsed = t.TypesUsed.Intersect(typesChanged) +select new { t, typesChangedUsed } + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists types *unchanged* since the *baseline* +// but that use directly some *types* where code has been changed +// since the *baseline*. +// +// For such matched type, the code hasen't been changed, but still the overall +// behavior might have been changed. +// +// The query result includes types changed directly used, +//]]> + Types indirectly using one or several types changed +let typesChanged = Application.Types.Where(t => t.CodeWasChanged()).ToHashSetEx() + +// 'depth' represents a code metric defined on types using +// directly or indirectly any type where code was changed. +let depth = JustMyCode.Types.DepthOfIsUsingAny(typesChanged) + +from t in depth.DefinitionDomain where + !t.CodeWasChanged() && + !t.WasAdded() + +let typesChangedDirectlyUsed = t.TypesUsed.Intersect(typesChanged) +let depthOfUsingTypesChanged = depth[t] +orderby depthOfUsingTypesChanged + +select new { + t, + depthOfUsingTypesChanged, + typesChangedDirectlyUsed +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists types *unchanged* since the *baseline* +// but that **use directly or indirectly** some *types* where +// code has been changed since the *baseline*. +// +// For such matched type, the code hasen't been changed, but still the overall +// behavior might have been changed. +// +// The query result includes types changed directly used, and the **depth of usage** +// of types indirectly used, *depth of usage* as defined in the documentation of +// *DepthOfIsUsingAny()* NDepend API method: +// https://www.ndepend.com/api/webframe.html#NDepend.API~NDepend.CodeModel.ExtensionMethodsSequenceUsage~DepthOfIsUsingAny.html +//]]> + New methods +from m in Application.Methods where + !m.ParentType.WasAdded() && + m.WasAdded() && + !m.IsGeneratedByCompiler +select new { m, m.NbLinesOfCode } + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *methods* that have been added since the *baseline*. +//]]> + Methods removed +from m in codeBase.OlderVersion().Application.Methods where + !m.ParentType.WasRemoved() && + m.WasRemoved() && + !m.IsGeneratedByCompiler +select new { m, m.NbLinesOfCode } + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *methods* that have been removed since the *baseline*. +//]]> + Methods where code was changed +from m in Application.Methods where m.CodeWasChanged() +select new { + m, + m.NbLinesOfCode, + oldNbLinesOfCode = m.OlderVersion().NbLinesOfCode , + delta = (int?) m.NbLinesOfCode - m.OlderVersion().NbLinesOfCode +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *methods* in which, code has been changed since the *baseline*. +// +// To visualize changes in code, right-click a matched method and select: +// +// • Compare older and newer versions of source file +// +// • Compare older and newer versions disassembled with Reflector +//]]> + Methods directly calling one or several methods changed +let methodsChanged = Application.Methods.Where(m => m.CodeWasChanged()).ToHashSetEx() + +from m in JustMyCode.Methods.UsingAny(methodsChanged ) where + !m.CodeWasChanged() && + !m.WasAdded() +let methodsChangedCalled = m.MethodsCalled.Intersect(methodsChanged) +select new { + m, + methodsChangedCalled +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists methods *unchanged* since the *baseline* +// but that call directly some *methods* where code has been changed +// since the *baseline*. +// +// For such matched method, the code hasen't been changed, but still the overall +// behavior might have been changed. +// +// The query result includes methods changed directly used, +//]]> + Methods indirectly calling one or several methods changed +let methodsChanged = Application.Methods.Where(m => m.CodeWasChanged()).ToHashSetEx() + +// 'depth' represents a code metric defined on methods using +// directly or indirectly any method where code was changed. +let depth = JustMyCode.Methods.DepthOfIsUsingAny(methodsChanged) + +from m in depth.DefinitionDomain where + !m.CodeWasChanged() && + !m.WasAdded() + +let methodsChangedDirectlyUsed = m.MethodsCalled.Intersect(methodsChanged) +let depthOfUsingMethodsChanged = depth[m] +orderby depthOfUsingMethodsChanged + +select new { + m, + depthOfUsingMethodsChanged, + methodsChangedDirectlyUsed +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists methods *unchanged* since the *baseline* +// but that **use directly or indirectly** some *methods* where +// code has been changed since the *baseline*. +// +// For such matched method, the code hasen't been changed, but still the overall +// behavior might have been changed. +// +// The query result includes methods changed directly used, and the **depth of usage** +// of methods indirectly used, *depth of usage* as defined in the documentation of +// *DepthOfIsUsingAny()* NDepend API method: +// https://www.ndepend.com/api/webframe.html#NDepend.API~NDepend.CodeModel.ExtensionMethodsSequenceUsage~DepthOfIsUsingAny.html +//]]> + New fields +from f in Application.Fields where + !f.ParentType.WasAdded() && + f.WasAdded() && + !f.IsGeneratedByCompiler +select f + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *fields* that have been added since the *baseline*. +//]]> + Fields removed +from f in codeBase.OlderVersion().Application.Fields where + !f.ParentType.WasRemoved() && + f.WasRemoved() && + !f.IsGeneratedByCompiler +select f + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *fields* that have been removed since the *baseline*. +//]]> + Third party types that were not used and that are now used +from t in ThirdParty.Types where t.IsUsedRecently() +select new { + t, + t.Methods, + t.Fields, + t.TypesUsingMe +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *types* defined in **third-party assemblies**, that were not +// used at *baseline* time, and that are now used. +//]]> + Third party types that were used and that are not used anymore +from t in codeBase.OlderVersion().Types where t.IsNotUsedAnymore() +select new { + t, + t.Methods, + t.Fields, + TypesThatUsedMe = t.TypesUsingMe +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *types* defined in **third-party assemblies**, that were +// used at *baseline* time, and that are not used anymore. +//]]> + Third party methods that were not used and that are now used +from m in ThirdParty.Methods where + m.IsUsedRecently() && + !m.ParentType.IsUsedRecently() +select new { + m, + m.MethodsCallingMe +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *methods* defined in **third-party assemblies**, that were not +// used at *baseline* time, and that are now used. +//]]> + Third party methods that were used and that are not used anymore +from m in codeBase.OlderVersion().Methods where + m.IsNotUsedAnymore() && + !m.ParentType.IsNotUsedAnymore() +select new { + m, + MethodsThatCalledMe = m.MethodsCallingMe +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *methods* defined in **third-party assemblies**, that were +// used at *baseline* time, and that are not used anymore. +//]]> + Third party fields that were not used and that are now used +from f in ThirdParty.Fields where + f.IsUsedRecently() && + !f.ParentType.IsUsedRecently() +select new { + f, + f.MethodsUsingMe +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *fields* defined in **third-party assemblies**, that were not +// used at *baseline* time, and that are now used. +//]]> + Third party fields that were used and that are not used anymore +from f in codeBase.OlderVersion().Fields where + f.IsNotUsedAnymore() && + !f.ParentType.IsNotUsedAnymore() +select new { + f, + MethodsThatUsedMe = f.MethodsUsingMe +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *fields* defined in **third-party assemblies**, that were +// used at *baseline* time, and that are not used anymore. +//]]> + + + Most used types (Rank) +(from t in Application.Types + where !t.IsGeneratedByCompiler + orderby t.Rank descending + select new { t, t.Rank, t.TypesUsingMe }).Take(100) + +// +// **TypeRank** values are computed by applying +// the **Google PageRank** algorithm on the +// graph of types' dependencies. Types with +// high *Rank* are the most used ones. Not necessarily +// the ones with the most users types, but the ones +// used by many types, themselves having a lot of +// types users. +// +// See the definition of the TypeRank metric here: +// https://www.ndepend.com/docs/code-metrics#TypeRank +// +// This code query lists the 100 application types +// with the higher rank. +// +// The main consequence of being used a lot for a +// type is that each change (both *syntax change* +// and *behavior change*) will result in potentially +// a lot of **pain** since most types clients will be +// **impacted**. +// +// Hence it is preferable that types with higher +// *TypeRank*, are **interfaces**, that are typically +// less subject changes. +// +// Also interfaces avoid clients relying on +// implementations details. Hence, when the behavior of +// classes implementing an interface changes, this +// shouldn't impact clients of the interface. +// This is *in essence* the +// **Liskov Substitution Principle**. +// http://en.wikipedia.org/wiki/Liskov_substitution_principle +//]]> + Most used methods (Rank) +(from m in Application.Methods + where !m.IsGeneratedByCompiler + orderby m.Rank descending + select new { m, m.Rank, m.MethodsCallingMe }).Take(100) + +// +// **MethodRank** values are computed by applying +// the **Google PageRank** algorithm on the +// graph of methods' dependencies. Methods with +// high *Rank* are the most used ones. Not necessarily +// the ones with the most callers methods, but the ones +// called by many methods, themselves having a lot +// of callers. +// +// See the definition of the MethodRank metric here: +// https://www.ndepend.com/docs/code-metrics#MethodRank +// +// This code query lists the 100 application methods +// with the higher rank. +// +// The main consequence of being used a lot for a +// method is that each change (both *signature change* +// and *behavior change*) will result in potentially +// a lot of **pain** since most methods callers will be +// **impacted**. +// +// Hence it is preferable that methods with highest +// *MethodRank*, are **abstract methods**, that are +// typically less subject to signature changes. +// +// Also abstract methods avoid callers relying on +// implementations details. Hence, when the code +// of a method implementing an abstract method changes, +// this shouldn't impact callers of the abstract method. +// This is *in essence* the +// **Liskov Substitution Principle**. +// http://en.wikipedia.org/wiki/Liskov_substitution_principle +//]]> + Most used assemblies (#AssembliesUsingMe) +(from a in Assemblies orderby a.AssembliesUsingMe.Count() descending + select new { a, a.AssembliesUsingMe }).Take(100) + +// +// This code query lists the 100 *application* and *third-party* +// assemblies, with the higher number of assemblies users. +//]]> + Most used namespaces (#NamespacesUsingMe ) +(from n in Namespaces orderby n.NbNamespacesUsingMe descending + select new { n, n.NamespacesUsingMe }).Take(100) + +// +// This code query lists the 100 *application* and *third-party* +// namespaces, with the higher number of namespaces users. +//]]> + Most used types (#TypesUsingMe ) +(from t in Types orderby t.NbTypesUsingMe descending + where !t.IsGeneratedByCompiler + select new { t, t.TypesUsingMe }).Take(100) + +// +// This code query lists the 100 *application* and *third-party* +// types, with the higher number of types users. +//]]> + Most used methods (#MethodsCallingMe ) +(from m in Methods orderby m.NbMethodsCallingMe + where !m.IsGeneratedByCompiler + select new { m, m.MethodsCallingMe }).Take(100) + +// +// This code query lists the 100 *application* and *third-party* +// methods, with the higher number of methods callers. +//]]> + Namespaces that use many other namespaces (#NamespacesUsed ) +(from n in Application.Namespaces orderby n.NbNamespacesUsed descending + select new { n, n.NamespacesUsed }).Take(100) + +// +// This code query lists the 100 *application* namespaces +// with the higher number of namespaces used. +//]]> + Types that use many other types (#TypesUsed ) +(from t in Application.Types orderby t.NbTypesUsed descending + select new { t, t.TypesUsed, isMyCode = JustMyCode.Contains(t) }).Take(100) + +// +// This code query lists the 100 *application* types +// with the higher number of types used. +//]]> + Methods that use many other methods (#MethodsCalled ) +(from m in Application.Methods orderby m.NbMethodsCalled descending + select new { m, m.MethodsCalled, isMyCode = JustMyCode.Contains(m) }).Take(100) + +// +// This code query lists the 100 *application* methods +// with the higher number of methods called. +//]]> + High-level to low-level assemblies (Level) +from a in Application.Assemblies orderby a.Level descending +select new { a, a.Level } + +// +// This code query lists assemblies ordered by **Level** values. +// See the definition of the *AssemblyLevel* metric here: +// https://www.ndepend.com/docs/code-metrics#Level +//]]> + High-level to low-level namespaces (Level) +from n in Application.Namespaces orderby n.Level descending +select new { n, n.Level } + +// +// This code query lists namespaces ordered by **Level** values. +// See the definition of the *NamespaceLevel* metric here: +// https://www.ndepend.com/docs/code-metrics#Level +//]]> + High-level to low-level types (Level) +from t in Application.Types orderby t.Level descending +select new { t, t.Level } + +// +// This code query lists types ordered by **Level** values. +// See the definition of the *TypeLevel* metric here: +// https://www.ndepend.com/docs/code-metrics#Level +//]]> + High-level to low-level methods (Level) +from m in Application.Methods orderby m.Level descending +select new { m, m.Level } + +// +// This code query lists methods ordered by **Level** values. +// See the definition of the *MethodLevel* metric here: +// https://www.ndepend.com/docs/code-metrics#Level +//]]> + + + + Check that the assembly Asm1 is not using the assembly Asm2 +// ND9901:ExplicitId9901 +warnif count > 0 from a in Application.Assemblies where + a.IsUsing ("Asm2".AllowNoMatch().MatchAssembly()) && + (a.Name == @"Asm1") +select new { + a, + Debt = 30.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to be warned if a particular assembly is using +// another particular assembly. +// +// Such rule can be generated for assemblies **A** and **B**: +// +// • by right clicking the cell in the *Dependency Matrix* +// with **B** in row and **A** in column, +// +// • or by right-clicking the concerned arrow in the *Dependency +// Graph* from **A** to **B**, +// +// and in both cases, click the menu +// **Generate a code rule that warns if this dependency exists** +// +// The generated rule will look like this one. +// It is now up to you to adapt this rule to check exactly +// your needs. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that the namespace N1.N2 is not using the namespace N3.N4.N5 +// ND9902:ExplicitId9902 + +warnif count > 0 from n in Application.Namespaces where + n.IsUsing ("N3.N4.N5".AllowNoMatch().MatchNamespace()) && + (n.Name == @"N1.N2") +select new { + n, + Debt = 30.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to be warned if a particular namespace is using +// another particular namespace. +// +// Such rule can be generated for namespaces **A** and **B**: +// +// • by right clicking the cell in the *Dependency Matrix* +// with **B** in row and **A** in column, +// +// • or by right-clicking the concerned arrow in the *Dependency +// Graph* from **A** to **B**, +// +// and in both cases, click the menu +// **Generate a code rule that warns if this dependency exists** +// +// The generated rule will look like this one. +// It is now up to you to adapt this rule to check exactly +// your needs. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that the assembly Asm1 is only using the assemblies Asm2, Asm3 or mscorlib +// ND9903:ExplicitId9903 + +warnif count > 0 from a in Application.Assemblies where + ( !a.IsUsing ("Asm2".AllowNoMatch().MatchAssembly()) || + !a.IsUsing ("Asm3".AllowNoMatch().MatchAssembly()) || + !a.IsUsing ("mscorlib".MatchAssembly()) || + a.AssembliesUsed.Count() != 3) // Must not be used more than 3 assemblies +&& + (a.Name == @"Asm1") +select new { + a, + a.AssembliesUsed, + Debt = 30.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to enforce that a particular assembly +// is only using a particular set of assemblies. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that the namespace N1.N2 is only using the namespaces N3.N4, N5 or System +// ND9904:ExplicitId9904 + +warnif count > 0 from n in Application.Namespaces where + ( !n.IsUsing("N3.N4".AllowNoMatch().MatchNamespace()) || + !n.IsUsing("N5".AllowNoMatch().MatchNamespace()) || + !n.IsUsing("System".MatchNamespace()) || + n.NamespacesUsed.Count() != 3) // Must not be used more than 3 assemblies + // AsmCe = Efferent Coupling for assembly +&& + (n.Name == @"N1.N2") +select new { + n, + n.NamespacesUsed, + Debt = 30.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to enforce that a particular namespace +// **is only using** a particular set of namespaces. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that AsmDrawing is the only assembly that is using System.Drawing +// ND9905:ExplicitId9905 + +warnif count> 0 from a in Application.Assemblies where + a.IsUsing ("System.Drawing".AllowNoMatch().MatchAssembly()) && + !(a.Name == @"AsmDrawing") +select new { + a, + Debt = 30.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to enforce that a particular assembly +// is **only used by** another particular assembly. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that only 3 assemblies are using System.Drawing +// ND9906:ExplicitId9906 + +warnif count != 3 from a in Application.Assemblies where + a.IsUsing ("System.Drawing".AllowNoMatch().MatchAssembly()) +select new { + a, + Debt = 30.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to enforce that a particular assembly +// is **only used by** 3 any others assemblies. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that all methods that call Foo.Fct1() also call Foo.Fct2(Int32) +// ND9907:ExplicitId9907 + +warnif count > 0 from m in Application.Methods where + m.IsUsing ("Foo.Fct1()".AllowNoMatch()) && + !m.IsUsing ("Foo.Fct2(Int32)".AllowNoMatch()) +select new { + m, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to enforce that if a method calls a particular method, +// it must call another particular method. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that all types that derive from Foo, also implement IFoo +// ND9908:ExplicitId9908 + +warnif count > 0 from t in Application.Types where + t.DeriveFrom ("Foo".AllowNoMatch().MatchType()) && + !t.Implement ("IFoo".AllowNoMatch().MatchType()) +select new { + t, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to enforce that all classes that derive from a particular base class, +// also implement a particular interface. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that all types that has the attribute FooAttribute are declared in the namespace N1.N2* +// ND9909:ExplicitId9909 + +warnif count > 0 from t in + Application.Namespaces.WithNameWildcardMatchNotIn("N1.N2*").ChildTypes() + where + t.HasAttribute ("FooAttribute".AllowNoMatch()) +select new { + t, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to enforce that all types that are tagged +// with a particular attribute, are declared in a +// particular namespace. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that all synchronization objects are only used from the namespaces under MyNamespace.Sync +// ND9910:ExplicitId9910 + +warnif count > 0 from n in Application.Namespaces + where + (n.IsUsing ("System.Threading.Monitor".AllowNoMatch()) || + n.IsUsing ("System.Threading.ReaderWriterLock".AllowNoMatch()) || + n.IsUsing ("System.Threading.Mutex".AllowNoMatch()) || + n.IsUsing ("System.Threading.EventWaitHandle".AllowNoMatch()) || + n.IsUsing ("System.Threading.Semaphore".AllowNoMatch()) || + n.IsUsing ("System.Threading.Interlocked".AllowNoMatch())) + && !n.NameLike (@"^MyNamespace.Sync") +select new { + n, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to enforce that all synchronization objects +// are used from a particular namespace. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + + + Check that the namespace N1.N2 is 100% covered by tests +// ND9911:ExplicitId9911 + +warnif count > 0 from n in Application.Namespaces where + (n.Name == @"N1.N2") && + n.PercentageCoverage < 100 +select new { + n, + n.PercentageCoverage, + Debt = n.NbLinesOfCodeNotCovered.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This is a sample rule that shows how to check +// if a particular namespace is 100% covered by tests. +// Both the string **@"N1.N2"** and the threshold **100** can be adapted to your own needs. +// +// To execute this sample rule, coverage data must be imported. +// More info here: https://www.ndepend.com/docs/code-coverage +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that the assembly Asm is 100% covered by tests +// ND9912:ExplicitId9912 + +warnif count > 0 from a in Application.Assemblies where + (a.Name == @"Asm") && + a.PercentageCoverage < 100 +select new { + a, + a.PercentageCoverage, + Debt = a.NbLinesOfCodeNotCovered.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This is a sample rule that shows how to check +// if a particular assembly is 100% covered by tests. +// Both the string **@"Asm"** and the threshold **100** can be adapted to your own needs. +// +// To execute this sample rule, coverage data must be imported. +// More info here: https://www.ndepend.com/docs/code-coverage +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that the class Namespace.Foo is 100% covered by tests +// ND9913:ExplicitId9913 + +warnif count > 0 from t in Application.Types where + (t.FullName == @"Namespace.Foo") && + t.PercentageCoverage < 100 +select new { + t, + t.PercentageCoverage, + Debt = t.NbLinesOfCodeNotCovered.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This is a sample rule that shows how to check +// if a particular class is 100% covered by tests. +// Both the string **@"Namespace.Foo"** and the threshold **100** +// can be adapted to your own needs. +// +// To execute this sample rule, coverage data must be imported. +// More info here: https://www.ndepend.com/docs/code-coverage +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that the class Namespace.Foo.Method(Int32) is 100% covered by tests +// ND9914:ExplicitId9914 + +warnif count > 0 from t in Application.Types where + (t.FullName == @"Namespace.Foo.Method(Int32)") && + t.PercentageCoverage < 100 +select new { + t, + t.PercentageCoverage, + Debt = t.NbLinesOfCodeNotCovered.ToMinutes().ToDebt(), + Severity = Severity.High +} + + +// +// This is a sample rule that shows how to check +// if a particular method is 100% covered by tests. +// Both the string **@"Namespace.Foo.Method(Int32)"** and the threshold **100** +// can be adapted to your own needs. +// +// To execute this sample rule, coverage data must be imported. +// More info here: https://www.ndepend.com/docs/code-coverage +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + + + Check that all types that derive from Foo, has a name that ends up with Foo +// ND9915:ExplicitId9915 + +warnif count > 0 from t in Application.Types where + t.DeriveFrom ("Foo".AllowNoMatch().MatchType()) && + !t.NameLike (@"Foo$") +select new { + t, + t.NbLinesOfCode, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to enforce that all classes that derive from +// a particular class, are named with a particular suffix. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that all namespaces begins with CompanyName.ProductName +// ND9916:ExplicitId9916 + +warnif count > 0 from n in Application.Namespaces where + !n.NameLike (@"^CompanyName.ProductName") +select new { + n, + n.NbLinesOfCode, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// A practice widely adopted is that, in a product source code, +// all namespaces start with "CompanyName.ProductName". +// +// This rule must be adapted with your own **"CompanyName.ProductName"**. +// + +// +// Update all namespaces definitions in source code to satisfy this rule. +//]]> + + + + + \ No newline at end of file From f693241b0661434afe2b5cfcceefe5c123c50b76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Terje=20Sandstr=C3=B8m?= Date: Mon, 24 May 2021 21:20:41 +0200 Subject: [PATCH 2/4] Seeds as properties #843 --- src/NUnitTestAdapter/AdapterSettings.cs | 72 ++++++++++--------- src/NUnitTestAdapter/CategoryList.cs | 4 +- .../NUnitEngine/NUnitTestNode.cs | 6 +- .../Properties/AssemblyInfo.cs | 4 +- src/NUnitTestAdapter/Seed.cs | 11 +++ src/NUnitTestAdapter/TestConverter.cs | 6 ++ src/NUnitTestAdapter/VsTestFilter.cs | 2 +- 7 files changed, 66 insertions(+), 39 deletions(-) create mode 100644 src/NUnitTestAdapter/Seed.cs diff --git a/src/NUnitTestAdapter/AdapterSettings.cs b/src/NUnitTestAdapter/AdapterSettings.cs index c8ca92b3..d812da12 100644 --- a/src/NUnitTestAdapter/AdapterSettings.cs +++ b/src/NUnitTestAdapter/AdapterSettings.cs @@ -296,25 +296,11 @@ public void Load(string settingsXml) Verbosity = GetInnerTextAsInt(nunitNode, nameof(Verbosity), 0); _logger.Verbosity = Verbosity; - var runConfiguration = doc.SelectSingleNode("RunSettings/RunConfiguration"); - MaxCpuCount = GetInnerTextAsInt(runConfiguration, nameof(MaxCpuCount), -1); - ResultsDirectory = GetInnerTextWithLog(runConfiguration, nameof(ResultsDirectory)); - TargetPlatform = GetInnerTextWithLog(runConfiguration, nameof(TargetPlatform)); - TargetFrameworkVersion = GetInnerTextWithLog(runConfiguration, nameof(TargetFrameworkVersion)); - TestAdapterPaths = GetInnerTextWithLog(runConfiguration, nameof(TestAdapterPaths)); - CollectSourceInformation = GetInnerTextAsBool(runConfiguration, nameof(CollectSourceInformation), true); - DisableAppDomain = GetInnerTextAsBool(runConfiguration, nameof(DisableAppDomain), false); - DisableParallelization = GetInnerTextAsBool(runConfiguration, nameof(DisableParallelization), false); - DesignMode = GetInnerTextAsBool(runConfiguration, nameof(DesignMode), false); - CollectDataForEachTestSeparately = - GetInnerTextAsBool(runConfiguration, nameof(CollectDataForEachTestSeparately), false); + ExtractRunConfiguration(doc); - TestProperties = new Dictionary(); - UpdateTestProperties(); + UpdateTestProperties(doc); // NUnit settings - InternalTraceLevel = GetInnerTextWithLog(nunitNode, nameof(InternalTraceLevel), "Off", "Error", "Warning", - "Info", "Verbose", "Debug"); WorkDirectory = GetInnerTextWithLog(nunitNode, nameof(WorkDirectory)); Where = GetInnerTextWithLog(nunitNode, nameof(Where)); DefaultTimeout = GetInnerTextAsInt(nunitNode, nameof(DefaultTimeout), 0); @@ -335,21 +321,16 @@ public void Load(string settingsXml) UseNUnitIdforTestCaseId = GetInnerTextAsBool(nunitNode, nameof(UseNUnitIdforTestCaseId), false); ConsoleOut = GetInnerTextAsInt(nunitNode, nameof(ConsoleOut), 1); // 0 no output to console, 1 : output to console StopOnError = GetInnerTextAsBool(nunitNode, nameof(StopOnError), false); - DiscoveryMethod = MapEnum(GetInnerText(nunitNode, nameof(DiscoveryMethod), Verbosity > 0), DiscoveryMethod.Current); UseNUnitFilter = GetInnerTextAsBool(nunitNode, nameof(UseNUnitFilter), true); // Engine settings + DiscoveryMethod = MapEnum(GetInnerText(nunitNode, nameof(DiscoveryMethod), Verbosity > 0), DiscoveryMethod.Current); SkipNonTestAssemblies = GetInnerTextAsBool(nunitNode, nameof(SkipNonTestAssemblies), true); AssemblySelectLimit = GetInnerTextAsInt(nunitNode, nameof(AssemblySelectLimit), 2000); - // Adapter Diagnostics - DumpXmlTestDiscovery = GetInnerTextAsBool(nunitNode, nameof(DumpXmlTestDiscovery), false); - DumpXmlTestResults = GetInnerTextAsBool(nunitNode, nameof(DumpXmlTestResults), false); - DumpVsInput = GetInnerTextAsBool(nunitNode, nameof(DumpVsInput), false); - FreakMode = GetInnerTextAsBool(nunitNode, nameof(FreakMode), false); - // End Diagnostics + ExtractNUnitDiagnosticSettings(nunitNode); // Adapter Display Options MapDisplayName(GetInnerText(nunitNode, nameof(DisplayName), Verbosity > 0)); @@ -357,8 +338,6 @@ public void Load(string settingsXml) // EndDisplay - - PreFilter = GetInnerTextAsBool(nunitNode, nameof(PreFilter), false); MapTestCategory(GetInnerText(nunitNode, nameof(VsTestCategoryType), Verbosity > 0)); MapWarningTo = MapWarningOutcome(GetInnerText(nunitNode, nameof(MapWarningTo), Verbosity > 0)); @@ -399,19 +378,46 @@ public void Load(string settingsXml) // Update NumberOfTestWorkers based on the DisableParallelization and NumberOfTestWorkers from runsettings. UpdateNumberOfTestWorkers(); + } + + private void ExtractNUnitDiagnosticSettings(XmlNode nunitNode) + { + DumpXmlTestDiscovery = GetInnerTextAsBool(nunitNode, nameof(DumpXmlTestDiscovery), false); + DumpXmlTestResults = GetInnerTextAsBool(nunitNode, nameof(DumpXmlTestResults), false); + DumpVsInput = GetInnerTextAsBool(nunitNode, nameof(DumpVsInput), false); + FreakMode = GetInnerTextAsBool(nunitNode, nameof(FreakMode), false); + InternalTraceLevel = GetInnerTextWithLog(nunitNode, nameof(InternalTraceLevel), "Off", "Error", "Warning", + "Info", "Verbose", "Debug"); + } - void UpdateTestProperties() + private void UpdateTestProperties(XmlDocument doc) + { + TestProperties = new Dictionary(); + foreach (XmlNode node in doc.SelectNodes("RunSettings/TestRunParameters/Parameter")) { - foreach (XmlNode node in doc.SelectNodes("RunSettings/TestRunParameters/Parameter")) - { - var key = node.GetAttribute("name"); - var value = node.GetAttribute("value"); - if (key != null && value != null) - TestProperties.Add(key, value); - } + var key = node.GetAttribute("name"); + var value = node.GetAttribute("value"); + if (key != null && value != null) + TestProperties.Add(key, value); } } + private void ExtractRunConfiguration(XmlDocument doc) + { + var runConfiguration = doc.SelectSingleNode("RunSettings/RunConfiguration"); + MaxCpuCount = GetInnerTextAsInt(runConfiguration, nameof(MaxCpuCount), -1); + ResultsDirectory = GetInnerTextWithLog(runConfiguration, nameof(ResultsDirectory)); + TargetPlatform = GetInnerTextWithLog(runConfiguration, nameof(TargetPlatform)); + TargetFrameworkVersion = GetInnerTextWithLog(runConfiguration, nameof(TargetFrameworkVersion)); + TestAdapterPaths = GetInnerTextWithLog(runConfiguration, nameof(TestAdapterPaths)); + CollectSourceInformation = GetInnerTextAsBool(runConfiguration, nameof(CollectSourceInformation), true); + DisableAppDomain = GetInnerTextAsBool(runConfiguration, nameof(DisableAppDomain), false); + DisableParallelization = GetInnerTextAsBool(runConfiguration, nameof(DisableParallelization), false); + DesignMode = GetInnerTextAsBool(runConfiguration, nameof(DesignMode), false); + CollectDataForEachTestSeparately = + GetInnerTextAsBool(runConfiguration, nameof(CollectDataForEachTestSeparately), false); + } + private void MapTestCategory(string vsTestCategoryType) { if (vsTestCategoryType == null) diff --git a/src/NUnitTestAdapter/CategoryList.cs b/src/NUnitTestAdapter/CategoryList.cs index dab4d55b..8e60de07 100644 --- a/src/NUnitTestAdapter/CategoryList.cs +++ b/src/NUnitTestAdapter/CategoryList.cs @@ -55,9 +55,9 @@ public class CategoryList // If it's not empty, it shows up as “Explicit [value]” in Test Explorer. private const string ExplicitTraitValue = ""; - private readonly NUnitProperty explicitTrait = new NUnitProperty(ExplicitTraitName, ExplicitTraitValue); + private readonly NUnitProperty explicitTrait = new (ExplicitTraitName, ExplicitTraitValue); - private readonly List categorylist = new List(); + private readonly List categorylist = new (); private readonly TestCase testCase; private readonly IAdapterSettings settings; private readonly bool showInternalProperties; diff --git a/src/NUnitTestAdapter/NUnitEngine/NUnitTestNode.cs b/src/NUnitTestAdapter/NUnitEngine/NUnitTestNode.cs index adf77233..9f0e07f2 100644 --- a/src/NUnitTestAdapter/NUnitEngine/NUnitTestNode.cs +++ b/src/NUnitTestAdapter/NUnitEngine/NUnitTestNode.cs @@ -9,6 +9,7 @@ public interface INUnitTestNode string FullName { get; } string Name { get; } IEnumerable Properties { get; } + string Seed { get; } } public abstract class NUnitTestNode : INUnitTestNode @@ -17,10 +18,13 @@ public abstract class NUnitTestNode : INUnitTestNode public virtual string Id => Node.GetAttribute("id"); public string FullName => Node.GetAttribute("fullname"); public string Name => Node.GetAttribute("name"); + public bool IsNull => Node == null; - private readonly List properties = new List(); + private readonly List properties = new (); public IEnumerable Properties => properties; + public string Seed => Node.GetAttribute("seed"); + protected NUnitTestNode(XmlNode node) { Node = node; diff --git a/src/NUnitTestAdapter/Properties/AssemblyInfo.cs b/src/NUnitTestAdapter/Properties/AssemblyInfo.cs index 0c422b9c..6825a8ab 100644 --- a/src/NUnitTestAdapter/Properties/AssemblyInfo.cs +++ b/src/NUnitTestAdapter/Properties/AssemblyInfo.cs @@ -1,5 +1,5 @@ // **************************************************************** -// Copyright (c) 2011-2020 Charlie Poole, Terje Sandstrom. +// Copyright (c) 2011-2021 Charlie Poole, Terje Sandstrom. // **************************************************************** using System; @@ -12,7 +12,7 @@ [assembly: AssemblyDescription("A package containing the NUnit 3 TestAdapter for Visual Studio 2012 onwards. With this package you don't need to install the VSIX adapter package, and you don't need to upload the adapter to your Azure DevOps server.\r\n \r\nNote that this package ONLY contains the adapter, not the NUnit framework. You must also get the framework. You only need one such package for a solution. \r\n\r\nThe package works with Visual Studio 2012 and newer.\r\n")] [assembly: AssemblyCompany("NUnit Project")] [assembly: AssemblyProduct("NUnit3TestAdapter")] -[assembly: AssemblyCopyright("Copyright © 2011-2020 Charlie Poole, 2014-2020 Terje Sandstrom")] +[assembly: AssemblyCopyright("Copyright © 2011-2021 Charlie Poole, 2014-2021 Terje Sandstrom")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: CLSCompliant(false)] diff --git a/src/NUnitTestAdapter/Seed.cs b/src/NUnitTestAdapter/Seed.cs new file mode 100644 index 00000000..01489677 --- /dev/null +++ b/src/NUnitTestAdapter/Seed.cs @@ -0,0 +1,11 @@ +using Microsoft.VisualStudio.TestPlatform.ObjectModel; + +namespace NUnit.VisualStudio.TestAdapter +{ + public class Seed + { + internal static readonly TestProperty NUnitSeedProperty = TestProperty.Register( + "NUnit.Seed", + "Seed", typeof(string), TestPropertyAttributes.None, typeof(TestCase)); + } +} diff --git a/src/NUnitTestAdapter/TestConverter.cs b/src/NUnitTestAdapter/TestConverter.cs index 9bdcc63d..86e4f02c 100644 --- a/src/NUnitTestAdapter/TestConverter.cs +++ b/src/NUnitTestAdapter/TestConverter.cs @@ -209,6 +209,7 @@ private TestCase MakeTestCaseFromDiscoveryNode(NUnitDiscoveryTestCase testNode) } testCase.AddTraitsFromTestNode(testNode, TraitsCache, _logger, adapterSettings); + testCase.SetPropertyValue(Seed.NUnitSeedProperty, testNode.Seed.ToString()); return testCase; @@ -299,6 +300,11 @@ private VSTestResult GetBasicResult(INUnitTestEvent resultNode, IEnumerable= 5) + { + vsResult.Messages.Add(new TestResultMessage(TestResultMessage.StandardOutCategory, $"seed: {resultNode.Seed}")); + } FillResultFromOutputNodes(outputNodes, vsResult); diff --git a/src/NUnitTestAdapter/VsTestFilter.cs b/src/NUnitTestAdapter/VsTestFilter.cs index 3b634de7..bd960e01 100644 --- a/src/NUnitTestAdapter/VsTestFilter.cs +++ b/src/NUnitTestAdapter/VsTestFilter.cs @@ -62,7 +62,7 @@ static VsTestFilter() ["FullyQualifiedName"] = TestCaseProperties.FullyQualifiedName, ["Name"] = TestCaseProperties.DisplayName, ["TestCategory"] = CategoryList.NUnitTestCategoryProperty, - ["Category"] = CategoryList.NUnitTestCategoryProperty + ["Category"] = CategoryList.NUnitTestCategoryProperty, }; // Initialize the trait cache var priorityTrait = new NTrait("Priority", ""); From 6e8a8ac80acd121330119d3b6b5b4234ba18aaf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Terje=20Sandstr=C3=B8m?= Date: Mon, 24 May 2021 21:24:38 +0200 Subject: [PATCH 3/4] Updated copyright years (prepare for release) --- src/NUnitTestAdapter/AdapterSettings.cs | 2 +- src/NUnitTestAdapter/CategoryList.cs | 2 +- src/NUnitTestAdapter/EmbeddedAssemblyResolution.cs | 2 +- src/NUnitTestAdapter/Metadata/TypeInfo.cs | 2 +- src/NUnitTestAdapter/NUnit3TestDiscoverer.cs | 2 +- src/NUnitTestAdapter/NUnit3TestExecutor.cs | 2 +- src/NUnitTestAdapter/NUnitEngine/DiscoveryConverter.cs | 2 +- src/NUnitTestAdapter/NUnitEngine/DiscoveryException.cs | 2 +- src/NUnitTestAdapter/NUnitEngine/Extensions.cs | 2 +- src/NUnitTestAdapter/NUnitEngine/NUnitDiscoveryTestClasses.cs | 2 +- src/NUnitTestAdapter/NUnitEngine/NUnitEngineAdapter.cs | 2 +- src/NUnitTestAdapter/NUnitEngine/NUnitEventTestCase.cs | 2 +- src/NUnitTestAdapter/NUnitEngine/NUnitResults.cs | 2 +- src/NUnitTestAdapter/NUnitEngine/NUnitTestEvent.cs | 2 +- src/NUnitTestAdapter/NUnitEngine/NUnitTestEventHeader.cs | 2 +- src/NUnitTestAdapter/NUnitEngine/NUnitTestEventTestOutput.cs | 2 +- src/NUnitTestAdapter/NUnitEventListener.cs | 2 +- src/NUnitTestAdapter/NUnitTestAdapter.cs | 2 +- src/NUnitTestAdapter/NUnitTestFilterBuilder.cs | 2 +- src/NUnitTestAdapter/NavigationData.cs | 2 +- src/NUnitTestAdapter/NavigationDataProvider.cs | 2 +- src/NUnitTestAdapter/PackageSettings.cs | 2 +- src/NUnitTestAdapter/TestConverter.cs | 2 +- src/NUnitTestAdapter/TestConverterForXml.cs | 2 +- src/NUnitTestAdapter/TestLogger.cs | 2 +- src/NUnitTestAdapter/TraitsFeature.cs | 2 +- src/NUnitTestAdapter/VsTestFilter.cs | 2 +- src/NUnitTestAdapter/XmlHelper.cs | 2 +- src/NUnitTestAdapterTests/AdapterSettingsTests.cs | 2 +- src/NUnitTestAdapterTests/CurrentDirectoryTests.cs | 2 +- src/NUnitTestAdapterTests/Fakes/FakeDiscoveryContext.cs | 2 +- src/NUnitTestAdapterTests/Fakes/FakeRunSettings.cs | 2 +- src/NUnitTestAdapterTests/NUnitEngineTests/NUnitResultsTests.cs | 2 +- .../NUnitEngineTests/NUnitTestCaseTests.cs | 2 +- .../NUnitEngineTests/NUnitTestEventsTests.cs | 2 +- src/NUnitTestAdapterTests/NUnitEventListenerTests.cs | 2 +- src/NUnitTestAdapterTests/TestAdapterUtils.cs | 2 +- src/NUnitTestAdapterTests/TestConverterForXmlTests.cs | 2 +- src/NUnitTestAdapterTests/TestConverterTests.cs | 2 +- src/NUnitTestAdapterTests/TestDiscoveryTests.cs | 2 +- src/NUnitTestAdapterTests/TestExecutionTests.cs | 2 +- 41 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/NUnitTestAdapter/AdapterSettings.cs b/src/NUnitTestAdapter/AdapterSettings.cs index d812da12..1cf84970 100644 --- a/src/NUnitTestAdapter/AdapterSettings.cs +++ b/src/NUnitTestAdapter/AdapterSettings.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2014-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2014-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/CategoryList.cs b/src/NUnitTestAdapter/CategoryList.cs index 8e60de07..d86ac4f3 100644 --- a/src/NUnitTestAdapter/CategoryList.cs +++ b/src/NUnitTestAdapter/CategoryList.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2014-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2014-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/EmbeddedAssemblyResolution.cs b/src/NUnitTestAdapter/EmbeddedAssemblyResolution.cs index 7796312a..654cf8fa 100644 --- a/src/NUnitTestAdapter/EmbeddedAssemblyResolution.cs +++ b/src/NUnitTestAdapter/EmbeddedAssemblyResolution.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2014-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2014-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/Metadata/TypeInfo.cs b/src/NUnitTestAdapter/Metadata/TypeInfo.cs index 2a3f3fa4..3e98f1ab 100644 --- a/src/NUnitTestAdapter/Metadata/TypeInfo.cs +++ b/src/NUnitTestAdapter/Metadata/TypeInfo.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2018-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2018-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/NUnit3TestDiscoverer.cs b/src/NUnitTestAdapter/NUnit3TestDiscoverer.cs index f940729b..aa7e92f9 100644 --- a/src/NUnitTestAdapter/NUnit3TestDiscoverer.cs +++ b/src/NUnitTestAdapter/NUnit3TestDiscoverer.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2011-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2011-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/NUnit3TestExecutor.cs b/src/NUnitTestAdapter/NUnit3TestExecutor.cs index b61ab793..b3f07c86 100644 --- a/src/NUnitTestAdapter/NUnit3TestExecutor.cs +++ b/src/NUnitTestAdapter/NUnit3TestExecutor.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2011-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2011-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/NUnitEngine/DiscoveryConverter.cs b/src/NUnitTestAdapter/NUnitEngine/DiscoveryConverter.cs index 8c4d378d..472e042c 100644 --- a/src/NUnitTestAdapter/NUnitEngine/DiscoveryConverter.cs +++ b/src/NUnitTestAdapter/NUnitEngine/DiscoveryConverter.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2020-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2020-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/NUnitEngine/DiscoveryException.cs b/src/NUnitTestAdapter/NUnitEngine/DiscoveryException.cs index ca19d278..b58a7ba0 100644 --- a/src/NUnitTestAdapter/NUnitEngine/DiscoveryException.cs +++ b/src/NUnitTestAdapter/NUnitEngine/DiscoveryException.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2020-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2020-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/NUnitEngine/Extensions.cs b/src/NUnitTestAdapter/NUnitEngine/Extensions.cs index 1840c56e..38e01a4b 100644 --- a/src/NUnitTestAdapter/NUnitEngine/Extensions.cs +++ b/src/NUnitTestAdapter/NUnitEngine/Extensions.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2020-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2020-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/NUnitEngine/NUnitDiscoveryTestClasses.cs b/src/NUnitTestAdapter/NUnitEngine/NUnitDiscoveryTestClasses.cs index c914f62a..97898093 100644 --- a/src/NUnitTestAdapter/NUnitEngine/NUnitDiscoveryTestClasses.cs +++ b/src/NUnitTestAdapter/NUnitEngine/NUnitDiscoveryTestClasses.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2020-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2020-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/NUnitEngine/NUnitEngineAdapter.cs b/src/NUnitTestAdapter/NUnitEngine/NUnitEngineAdapter.cs index cad3e54a..1414aac2 100644 --- a/src/NUnitTestAdapter/NUnitEngine/NUnitEngineAdapter.cs +++ b/src/NUnitTestAdapter/NUnitEngine/NUnitEngineAdapter.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2020-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2020-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/NUnitEngine/NUnitEventTestCase.cs b/src/NUnitTestAdapter/NUnitEngine/NUnitEventTestCase.cs index fae20d01..0ade7fc5 100644 --- a/src/NUnitTestAdapter/NUnitEngine/NUnitEventTestCase.cs +++ b/src/NUnitTestAdapter/NUnitEngine/NUnitEventTestCase.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2020-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2020-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/NUnitEngine/NUnitResults.cs b/src/NUnitTestAdapter/NUnitEngine/NUnitResults.cs index 4cfe2b1a..84e5c238 100644 --- a/src/NUnitTestAdapter/NUnitEngine/NUnitResults.cs +++ b/src/NUnitTestAdapter/NUnitEngine/NUnitResults.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2020-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2020-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/NUnitEngine/NUnitTestEvent.cs b/src/NUnitTestAdapter/NUnitEngine/NUnitTestEvent.cs index 30e877e5..620a5604 100644 --- a/src/NUnitTestAdapter/NUnitEngine/NUnitTestEvent.cs +++ b/src/NUnitTestAdapter/NUnitEngine/NUnitTestEvent.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2020-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2020-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/NUnitEngine/NUnitTestEventHeader.cs b/src/NUnitTestAdapter/NUnitEngine/NUnitTestEventHeader.cs index 98088cf0..4080b55d 100644 --- a/src/NUnitTestAdapter/NUnitEngine/NUnitTestEventHeader.cs +++ b/src/NUnitTestAdapter/NUnitEngine/NUnitTestEventHeader.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2020-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2020-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/NUnitEngine/NUnitTestEventTestOutput.cs b/src/NUnitTestAdapter/NUnitEngine/NUnitTestEventTestOutput.cs index 893ac505..21a6cc7c 100644 --- a/src/NUnitTestAdapter/NUnitEngine/NUnitTestEventTestOutput.cs +++ b/src/NUnitTestAdapter/NUnitEngine/NUnitTestEventTestOutput.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2020-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2020-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/NUnitEventListener.cs b/src/NUnitTestAdapter/NUnitEventListener.cs index 4a11bf66..6c1e80c1 100644 --- a/src/NUnitTestAdapter/NUnitEventListener.cs +++ b/src/NUnitTestAdapter/NUnitEventListener.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2011-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2011-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/NUnitTestAdapter.cs b/src/NUnitTestAdapter/NUnitTestAdapter.cs index d1181966..0ab4119d 100644 --- a/src/NUnitTestAdapter/NUnitTestAdapter.cs +++ b/src/NUnitTestAdapter/NUnitTestAdapter.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2011-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2011-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/NUnitTestFilterBuilder.cs b/src/NUnitTestAdapter/NUnitTestFilterBuilder.cs index 006dd71f..1ac3e1f2 100644 --- a/src/NUnitTestAdapter/NUnitTestFilterBuilder.cs +++ b/src/NUnitTestAdapter/NUnitTestFilterBuilder.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2011-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2011-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/NavigationData.cs b/src/NUnitTestAdapter/NavigationData.cs index e030748e..166c1f30 100644 --- a/src/NUnitTestAdapter/NavigationData.cs +++ b/src/NUnitTestAdapter/NavigationData.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2016-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2016-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/NavigationDataProvider.cs b/src/NUnitTestAdapter/NavigationDataProvider.cs index d51e7d97..2f920bb8 100644 --- a/src/NUnitTestAdapter/NavigationDataProvider.cs +++ b/src/NUnitTestAdapter/NavigationDataProvider.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2018-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2018-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/PackageSettings.cs b/src/NUnitTestAdapter/PackageSettings.cs index 105e63e4..6833366c 100644 --- a/src/NUnitTestAdapter/PackageSettings.cs +++ b/src/NUnitTestAdapter/PackageSettings.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2014-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2014-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/TestConverter.cs b/src/NUnitTestAdapter/TestConverter.cs index 86e4f02c..75190c9c 100644 --- a/src/NUnitTestAdapter/TestConverter.cs +++ b/src/NUnitTestAdapter/TestConverter.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2011-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2011-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/TestConverterForXml.cs b/src/NUnitTestAdapter/TestConverterForXml.cs index dc4f4742..61527c78 100644 --- a/src/NUnitTestAdapter/TestConverterForXml.cs +++ b/src/NUnitTestAdapter/TestConverterForXml.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2011-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2011-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/TestLogger.cs b/src/NUnitTestAdapter/TestLogger.cs index 708ee5cb..2623cfe4 100644 --- a/src/NUnitTestAdapter/TestLogger.cs +++ b/src/NUnitTestAdapter/TestLogger.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2013-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2013-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/TraitsFeature.cs b/src/NUnitTestAdapter/TraitsFeature.cs index 4909e2bb..69586717 100644 --- a/src/NUnitTestAdapter/TraitsFeature.cs +++ b/src/NUnitTestAdapter/TraitsFeature.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2013-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2013-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/VsTestFilter.cs b/src/NUnitTestAdapter/VsTestFilter.cs index bd960e01..b20a7eb7 100644 --- a/src/NUnitTestAdapter/VsTestFilter.cs +++ b/src/NUnitTestAdapter/VsTestFilter.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2013-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2013-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapter/XmlHelper.cs b/src/NUnitTestAdapter/XmlHelper.cs index 17e68637..ee3da2cd 100644 --- a/src/NUnitTestAdapter/XmlHelper.cs +++ b/src/NUnitTestAdapter/XmlHelper.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2010-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2010-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapterTests/AdapterSettingsTests.cs b/src/NUnitTestAdapterTests/AdapterSettingsTests.cs index 47399985..7a035bfd 100644 --- a/src/NUnitTestAdapterTests/AdapterSettingsTests.cs +++ b/src/NUnitTestAdapterTests/AdapterSettingsTests.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2011-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2011-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapterTests/CurrentDirectoryTests.cs b/src/NUnitTestAdapterTests/CurrentDirectoryTests.cs index d85c81bd..3e0aef6b 100644 --- a/src/NUnitTestAdapterTests/CurrentDirectoryTests.cs +++ b/src/NUnitTestAdapterTests/CurrentDirectoryTests.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2019-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2019-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapterTests/Fakes/FakeDiscoveryContext.cs b/src/NUnitTestAdapterTests/Fakes/FakeDiscoveryContext.cs index 60c69ebf..4bfbcd36 100644 --- a/src/NUnitTestAdapterTests/Fakes/FakeDiscoveryContext.cs +++ b/src/NUnitTestAdapterTests/Fakes/FakeDiscoveryContext.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2012-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2012-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapterTests/Fakes/FakeRunSettings.cs b/src/NUnitTestAdapterTests/Fakes/FakeRunSettings.cs index 678d504d..50452c8d 100644 --- a/src/NUnitTestAdapterTests/Fakes/FakeRunSettings.cs +++ b/src/NUnitTestAdapterTests/Fakes/FakeRunSettings.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2012-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2012-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitResultsTests.cs b/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitResultsTests.cs index ee12aec9..5e8e8e6e 100644 --- a/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitResultsTests.cs +++ b/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitResultsTests.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2020-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2020-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitTestCaseTests.cs b/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitTestCaseTests.cs index b1494a40..57d7a8db 100644 --- a/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitTestCaseTests.cs +++ b/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitTestCaseTests.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2020-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2020-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitTestEventsTests.cs b/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitTestEventsTests.cs index f9ac479e..4a2400bb 100644 --- a/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitTestEventsTests.cs +++ b/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitTestEventsTests.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2020-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2020-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapterTests/NUnitEventListenerTests.cs b/src/NUnitTestAdapterTests/NUnitEventListenerTests.cs index 0ca1887a..81f4e967 100644 --- a/src/NUnitTestAdapterTests/NUnitEventListenerTests.cs +++ b/src/NUnitTestAdapterTests/NUnitEventListenerTests.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2012-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2012-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapterTests/TestAdapterUtils.cs b/src/NUnitTestAdapterTests/TestAdapterUtils.cs index 8ac7ae76..5d7761cb 100644 --- a/src/NUnitTestAdapterTests/TestAdapterUtils.cs +++ b/src/NUnitTestAdapterTests/TestAdapterUtils.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2018-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2018-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapterTests/TestConverterForXmlTests.cs b/src/NUnitTestAdapterTests/TestConverterForXmlTests.cs index f3f7bf0d..d57b22ce 100644 --- a/src/NUnitTestAdapterTests/TestConverterForXmlTests.cs +++ b/src/NUnitTestAdapterTests/TestConverterForXmlTests.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2011-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2011-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapterTests/TestConverterTests.cs b/src/NUnitTestAdapterTests/TestConverterTests.cs index ba3eb6f7..15c3d742 100644 --- a/src/NUnitTestAdapterTests/TestConverterTests.cs +++ b/src/NUnitTestAdapterTests/TestConverterTests.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2011-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2011-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapterTests/TestDiscoveryTests.cs b/src/NUnitTestAdapterTests/TestDiscoveryTests.cs index b9bade7c..cf7e4419 100644 --- a/src/NUnitTestAdapterTests/TestDiscoveryTests.cs +++ b/src/NUnitTestAdapterTests/TestDiscoveryTests.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2011-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2011-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the diff --git a/src/NUnitTestAdapterTests/TestExecutionTests.cs b/src/NUnitTestAdapterTests/TestExecutionTests.cs index f27fa18f..e768fa0d 100644 --- a/src/NUnitTestAdapterTests/TestExecutionTests.cs +++ b/src/NUnitTestAdapterTests/TestExecutionTests.cs @@ -1,5 +1,5 @@ // *********************************************************************** -// Copyright (c) 2012-2020 Charlie Poole, Terje Sandstrom +// Copyright (c) 2012-2021 Charlie Poole, Terje Sandstrom // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the From d898489649c140a8537928381f7935c3cc39f43c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Terje=20Sandstr=C3=B8m?= Date: Thu, 27 May 2021 21:43:56 +0200 Subject: [PATCH 4/4] FIx for Issue #852 NullReferenceException in ExtractTestFixture in v4.0.0-beta2 --- .../NUnitEngine/DiscoveryConverter.cs | 2 +- .../NUnitEngineTests/NUnitDiscoveryTests.cs | 32 +++++++++++++++++++ .../NUnitEngineTests/NUnitTestEventsTests.cs | 2 +- .../NUnitEventListenerOutputTests.cs | 4 +-- 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/NUnitTestAdapter/NUnitEngine/DiscoveryConverter.cs b/src/NUnitTestAdapter/NUnitEngine/DiscoveryConverter.cs index 472e042c..17821fce 100644 --- a/src/NUnitTestAdapter/NUnitEngine/DiscoveryConverter.cs +++ b/src/NUnitTestAdapter/NUnitEngine/DiscoveryConverter.cs @@ -235,7 +235,7 @@ private static void ExtractAllFixtures(NUnitDiscoveryTestSuite parent, XElement private static void ExtractTestFixtures(NUnitDiscoveryCanHaveTestFixture parent, XElement node) { - foreach (var child in node.Elements()) + foreach (var child in node.Elements().Where(o => o.Name != "properties")) { var type = child.Attribute(NUnitXmlAttributeNames.Type).Value; var className = child.Attribute(NUnitXmlAttributeNames.Classname)?.Value; diff --git a/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitDiscoveryTests.cs b/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitDiscoveryTests.cs index 15e39530..dc5cadf8 100644 --- a/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitDiscoveryTests.cs +++ b/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitDiscoveryTests.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Xml; using NSubstitute; using NUnit.Framework; using NUnit.VisualStudio.TestAdapter.NUnitEngine; @@ -1114,5 +1115,36 @@ public void ThatSetUpFixtureWorksIssue824() new NUnitResults(XmlHelper.CreateXmlNode(SetupFixtureIssue824))); Assert.That(ndr, Is.Not.Null); } + + private const string ExtractFixturesHandlesProperties = + @" + + + + + + + + + + + + + + + + +"; + + [Test] + public void ThatExtractFixturesHandlesProperties() + { + var sut = new DiscoveryConverter(logger, settings); + XmlNode node = null; + Assert.DoesNotThrow(() => node = XmlHelper.CreateXmlNode(ExtractFixturesHandlesProperties)); + var ndr = sut.ConvertXml( + new NUnitResults(node)); + Assert.That(ndr, Is.Not.Null); + } } } diff --git a/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitTestEventsTests.cs b/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitTestEventsTests.cs index 4a2400bb..12ccaf1c 100644 --- a/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitTestEventsTests.cs +++ b/src/NUnitTestAdapterTests/NUnitEngineTests/NUnitTestEventsTests.cs @@ -303,7 +303,7 @@ public void ThatTestCaseFailsCanBeParsedWithReason() @""; /// - /// Issue 811 + /// Issue 811. /// [Test] public void ThatExplicitTestFixtureWorksWithZeroStartTime() diff --git a/src/NUnitTestAdapterTests/NUnitEventListenerOutputTests.cs b/src/NUnitTestAdapterTests/NUnitEventListenerOutputTests.cs index 5dd19aed..9f323064 100644 --- a/src/NUnitTestAdapterTests/NUnitEventListenerOutputTests.cs +++ b/src/NUnitTestAdapterTests/NUnitEventListenerOutputTests.cs @@ -39,7 +39,7 @@ public class NUnitEventListenerOutputTests @""; /// - /// For Issue 811 + /// For Issue 811. /// private const string TestFinishWithExplicitFixture = @""; @@ -89,7 +89,7 @@ public void ThatTestOutputWithOnlyWhiteSpaceIsNotOutput() } /// - /// Issue 811 System.FormatException: The UTC representation of the date falls outside the year range 1-9999" from skipped test in Eastern European time zone + /// Issue 811 System.FormatException: The UTC representation of the date falls outside the year range 1-9999" from skipped test in Eastern European time zone. /// [Test] public void ThatExplicitTestFixtureWorksWithZeroStartTime()