Declare Active Conventions inside C# or VB.NET Source Code

With the last version of NDepend, we polished one particular scenario that I would like to describe properly: The possibility to store CQL rules inside source code.

CQL constraints of an NDepend project are stored as raw text in the XML project file. CQL constraints can also be stored in C# or VB.NET source code. This facility is especially useful when a constraint implies a code element.

Consider this piece of code:

 

In the CQL query editor in VisualNDepend.exe, let’s write a custom convention that checks that:

When a method is calling the method Foo1(), it must also call the method Foo2(int).


Let’s declare this constraint in the C# source code. Right click the editor and select: Copy to clipboard to insert this query in C# source 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 CQL constraint as a string.


It makes sense to declare such constraint near the Foo1() / Foo2(int) methods definitions. This way, during code review or refactoring developers cannot 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 constraint when finding a CQL constraint in source code.

The attribute class is CQLContraintAttribute declared in the namespace NDepend.CQL declared in the assembly NDepend.CQL.dll. This assembly can be found in $NDepend install dir$/Lib. It is freely redistributable. For conveniency, it is also very small, less than 10KB. Don’t forget to reference the assembly NDepend.CQL.dll from your project.


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

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

There is also the facility to Go to Constraint Declaration in Source Code:

 

 

 

Tip 1:

You can use the tag $FullName$ inside the CQL constraint 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$ includes the namespace and the type name.

 

In both C# and VB.NET, a namespace’s declaration cannot be tagged with an attribute.

 

Tip 2:

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


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

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


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

ImmutableAttribute 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.

 

GeneratedAttribute to tag generated code. This way it is easy to differentiate generated code and handcrafted code while writing constraints.

 

CouldNotBeInternalAttribute to tag public classes or methods deemed as CoulbBeInternal but that cannot be internal for some reasons (such as some elements must be public for some designer or for tools such as serializer or unit tests runner).

The default set of CQL constraints contains constraints associated with these attributes.
It is up to you to define your own attributes classes and your own constraints for your own needs.

 

Update: Tip 3:  

The first comment on the present blog post was from Joshua Flanagan that would like not to bind with NDepend.CQL.dll but instead define its own attribute. This is possible by just copy-pasting the following attribute class inside your project. It is working like a charm as long as you don’t tinker anything (namespace name, property name…).

 

 

This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • C-J Berg

    What I meant to write: Add a “Copy to clipboard […] with #region”… :-)

  • C-J Berg

    I like the idea of inline documentation that can be automatically verified and enforced. Great stuff!

    A few small ideas:

    Add $namespace$/$type$/$fulltype$ tags so you can write, e.g., “”$namespace$.Foo.Foo2(Int32)”" or “”$fulltype$.Foo2(Int32)”". It would make refactoring less painful.

    Ship the sources of NDepend.CQL.dll alongside NDepend. That would make it easier to keep the code up-to-date for all who wants to follow Tip 3. (Personally, I will reference the assembly and let ILMerge bake it into the target assembly created for deployment builds.)

    Add a “Copy to region […] with #region” that surrounds the attribute with a region declaration, named for instance “CQL: ”. Preferably make the naming configurable in Options.

    Add a language preference in Options, so you can choose to display only C# or VB.NET (or both) alternatives in context menus. It would make the menus less cluttered for those of us who primarily code in only one of the languages.

  • http://www.NDepend.com Patrick Smacchia

    Joshua,

    We didn’t think of this scenario but as mentionned in the Tip 3 that I just add you can do it :o)

    This is possible just because we use Mono.Cecil and not System.Reflection. Thus, the introspection done with Cecil is only done with attribute type and members names.

    One more time: thanks Mono.Cecil!

  • http://flimflan.com/blog Joshua Flanagan

    I appreciate that you kept NDepend.CQL small and separate, but it still makes me a little uncomfortable to have my project reference an assembly just for analysis purposes.
    I wonder if you might be able to achieve the same thing by allowing developers to create their own attribute with a specific name, even though it is not in your assembly (like the .NET 2.0 extension method trick).
    Or allow developers to come up with their own custom attribute name for this purpose, and allow them to specify that name as one of the NDepend project settings.

    I don’t currently use this feature (embedded CQL rules), but I’d be more likely to consider it if the reference to your assemblies wasn’t necessary.