Declare CQLinq rules in C# or VB.NET source code

CQLinq rules of an NDepend project are stored as raw text in the XML project file (.ndproj). CQLinq rules can also be stored in C# or VB.NET source code. This facility is especially useful when a rule implies a code element.

Consider this piece of code:

csharp code

In the CQLinq query editor in VisualNDepend.exe, let's write a custom rule that checks that when a method is calling the method Foo1(), it must also call the method Foo2(int).

ndepend cqlinq csharp linq code rule

Let's declare this rule in the C# source code. Right click the editor and select: Copy to clipboard to insert this query in C# source code ...

ndepend cqlinq csharp linq code rule copy to clipboard fo insert in csharp code

... and paste the clipboard content just before the Fct1() method definition. An attribute is now tagging the Fct1() method definition. This attribute contains our CQLinq rule as a string.

It makes sense to declare such rule near the Foo1() / Foo2(int) methods definitions. This way, during code review or refactoring developers couldn't miss it. This is active documentation. The same way the C# compiler checks privacy when finding the private keyword in source code, the NDepend analyzer checks a custom rule when finding a CQLinq rule in source code.

ndepend cqlinq csharp linq code rule defined in csharp source code

The attribute class is CodeRuleAttribute declared in the namespace NDepend.Attributes declared in the assembly NDepend.API.dll.

ndepend coderuleattribute defined in the assembly ndepend.api.dll

Each time the project will be analyzed, the CQLinq rule is now extraced and included in the list. A dedicated group named Rules extracted from Source Code is automatically created. This group and its children are read-only.

Rules extracted are organized by assemblies / namespaces where they are declared.

Notice the facility to Go to Rule Definition in Source Code:

ndepend go to rule definition in source code

Tips 1: You can use the tag $FullName$ inside the CQLinq rule text. It will be automatically replaced by the full name of the code element tagged. There is also the tag $Name$:

  • If an assembly is tagged, $Name$ and $FullName$ will be both replaced with the assembly name (without .dll or .exe extension).
  • If a type is tagged, $Name$ is the name of the type without the namespace and $FullName$ include the namespace.
  • If a method or a field is tagged, $Name$ is the name of the member (including method signature for method) and $FullName$ include the namespace and the type name.
  • In both C# and VB.NET, a namespace's declaration cannot be tagged with an attribute.
tag $fullname$ for code rule defined in source code

Tips 2: It can be tedious to define a rule for each code element that must satisfy a particular rule. For example, we want to avoid writing dozens of rules to check the full test coverage of dozens of types. Such scenario can be handled by defining a dedicated attribute such as the NDepend.Attributes.FullCoveredAttribute. Let's tag each class or structure 100% covered by tests with the FullCoveredAttribute...

fullcoveredattribute to explicitly declare that a code element must be 100% covered by tests

Now, the full coverage of classes and structures can be verified with a single CQLinq rule:

// <Name>Types tagged with FullCoveredAttribute should be 100% covered</Name>
warnif count > 0 
from t in Application.Types where
  
t.HasAttribute ("NDepend.Attributes.FullCoveredAttribute".AllowNoMatch()) &&
  
t.PercentageCoverage < 100

let notFullCoveredMethods = t.Methods.Where(
    
m =>  m.NbLinesOfCode> 0 &&  
          
m.PercentageCoverage < 100 &&
         
!m.HasAttribute("NDepend.Attributes.UncoverableByTestAttribute".AllowNoMatch()))

orderby t.NbLinesOfCodeNotCovered descending 

select new { t, t.PercentageCoverage, t.NbLinesOfCodeNotCovered, notFullCoveredMethods,
                
t.NbLinesOfCode, t.NbLinesOfCodeCovered }

// By using a FullCoveredAttribute, you can signify to developers
// that a class is, and must remain in the future, 100% covered by tests.
// If you don't want to link NDepend.API.dll, 
// you can use your own attribute and adapt this rule.

// Having types 100% covered by tests is a good idea because 
// the small portion of code hard to cover, is also the 
// portion of code that is the most likely to contain bugs.

The following query can be used to demand for types 100% covered by tests not tagged yet with the FullCoveredAttribute.

// <Name>Types 100% covered should be tagged with FullCoveredAttribute</Name>
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 }

// By using a FullCoveredAttribute, you can signify to developers
// that a class is, and must remain in the future, 100% covered by tests.
// If you don't want to link NDepend.API.dll, you can use your own attribute and adapt this rule.

// Having types 100% covered by tests is a good idea because 
// the small portion of code hard to cover, is also the 
// portion of code that is the most likely to contain bugs.

The FullCoveredAttribute and some other attributes are also defined in the NDepend.API.dll assembly. This includes:

  • FullCoveredAttribute to continuously check that a class or a structure is immutable.
  • PureAttribute to continuously check that a method is pure, i.e it doesn't provoke any side effect by modifying some fields states.
  • CannotDecreaseVisibilityAttribute to signify that despite a member could have a lower visibility without provoking any syntax error, your intention is to not change this member visibility.

Notice also the System.CodeDom.Compiler.GeneratedCodeAttribute that can be used to ignore generated classes in your code query and rules results.

The default set of CQLinq rules contains rules associated with these attributes.

It is up to you to define your own attributes classes and your own rules for your own needs.


Tips 3: If you don't want to bind with NDepend.API.dll you can copy-paste the following attribute class inside your source code. It is working as long as you don't modify anything (namespace name, class name, property name...).