CQLinq, Code Query LINQ, is a feature proposed by the tool JArchitect since the version 3, to query Java code base.
CQLinq syntax peculiarities are:
CQLinq Query edition
A CQLinq query can be edited live in the JArchitect UI.
The query is executed automatically as soon as it compiles.
Notice in the screenshot below the 9ms at the top right, that indicates the execution duration of the query.
CQLinq is fast and is designed to run hundreds of queries per seconds against a large real-world code base.
CQLinq edition comes also with code completion/intellisense, and also tooltip documentation on mouse hovering the query body.
|
Go to top
Predefined domains
CQLinq defines a few predefined domains to query on including: Types ; Methods ; Fields ; Packages ; Projects
These domains enumerate not only all code elements of the code base queried, but also all third-party code elements used by the code base (like for example the type string and all methods and fields of the type string that are used by the code base).
The syntax is as simple as:
from m in Methods where m.NbLinesOfCode > 30 select m
A CQLinq query can rely on one or several domains. Notice in the query above how the domain word Methods is highlighted differently.
There are two convenient predefined domains that are used often: Application and ThirdParty.
As their name suggest, these domains are useful to enumerate code elements defined only in application projects, or only defined in third-party projects (like STL, MFC or Boost) and used by the application code.
These two domains represent each a partial view of the entire code base.
from m in Application.Methods where m.NbLinesOfCode > 30 select m
It is easy to refine these predefined domains.
For example the query below matches large methods defined only in the package ProductName.FeatureA and its child packages:
from m in Application.Packages.WithNameLike("ProductName.FeatureA").ChildMethods()
where m.CyclomaticComplexity > 10 select m
Go to top
Defining the code base view JustMyCode with notmycode prefix
There is another convenient predefined domain named JustMyCode.
The domain JustMyCode represents a facility of CQLinq to eliminate generated code elements from CQLinq query results.
For example the following query will only match large methods that are not generated by a tool (like a UI designer):
from m in JustMyCode.Methods where m.NbLinesOfCode > 30 select m
The set of generated code elements is defined by CQLinq queries prefixed with the CQLinq keyword notmycode.
For example the query below matches methods defined in source files whose name contains a specific words.
notmycode from m in Methods where
m.SourceFileDeclAvailable &&
m.SourceDecls.First().SourceFile.FileName.ToLower().Contains("generated.cpp")
select m
The CQLinq queries runner executes all notmycode queries before queries relying on JustMyCode, hence the domain JustMyCode is defined once for all.
Obviously the CQLinq compiler emits an error if a notmycode query relies on the JustMyCode domain.
CQLinq code rules
A CQLinq query can be easily transformed into a rule by prefixing it with a condition defined with the two CQLinq keywords warnif count.
The keyword count is an unsigned integer that is equal to the number of code elements matched by the query.
For example the following query warns if some large methods are matched in the code base application methods:
// <Name>Avoid too large methods</Name> warnif count > 0 from m in Application.Methods where m.NbLinesOfCode > 30 select m
CQLinq code rules are useful to define which bad practices the team wants to avoid in the code base.
The team can see code rules violation warning in the JArchitect UI, or in the report.
The team has also the possibility to define some rules as critical rules.
|
With CQLinq both the query operator syntax and the query expression syntax are allowed.
The query operator syntax is the one with direct calls to methods like Where() and Select()...
Methods.Where(m => m.NbLinesOfCode > 30)
The query expressions syntax is the keywords like where and select...
from m in Methods where m.NbLinesOfCode > 30 select m
Often you'll find convenient to mix both syntaxes in one query.
- The query operator syntax is convenient to define sub-set and sub-domains.
- The query expression syntax is convenient to define operations on these sub-sets and sub-domains.
For example the query below defines with the query operator syntax the sub-set of methods defined in static types,
and use the query expression syntax to filter and project the large methods from this sub-set.
from m in Application.Types.Where(t => t.IsNested).ChildMethods()
where m.NbLinesOfCode > 30 select m
The CQLinq syntax present the facility to define range variables with the keyword let.
In this section, we wanted to underline this possibility because using the let keyword is a common practice when writing CQLinq queries.
For example, the following default rule define a custom code metrics thanks to several range variables:
// <Name>C.R.A.P method code metric</Name>
// Change Risk Analyzer and Predictor (i.e. CRAP) code metric
// This code metric helps in pinpointing overly complex and untested code.
// Reference: http://www.artima.com/weblogs/viewpost.jsp?thread=215899
// Formula: CRAP(m) = comp(m)^2 * (1 – cov(m)/100)^3 + comp(m)
warnif count > 0
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
orderby CRAP descending, m.NbLinesOfCode descending
select new { m, CRAP, CC, uncoveredPercentage = uncov*100, m.NbLinesOfCode }
Notice that using many let clauses in the main query loop, can significantly decrease performance of the query execution as explained here.
The CQLinq compiler extends the usage of the let keyword, because with CQLinq, the let keyword can be used to define a variable at the beginning of a CQLinq query.
For example, the default CQLinq rule below, first tries to match the BaseClass types, and if found, second let the query be executed.
warnif count > 0
let base = ThirdParty.Types.WithFullName("BaseClass").FirstOrDefault()
where base != null // base can be null if the code base doesn't use at all BaseClass
from t in Application.Types where
!t.DeriveFrom(base) select t
For some others CQLinq rules, it can be convenient to define multiple sub-sets through several let keyword expressions, before executing the query itself.
For example, the default CQLinq rules below, first define the sub-sets uiTypes and dbTypes before using them in the query code.
// <Name>UI layer shouldn't use directly DB types</Name>
warnif count > 0
// UI layer is made of types in packages using a UI framework
let uiTypes = Application.Packages.UsingAny(
Projects.WithNameIn("PresentationFramework", "MFC")
).ChildTypes()
// You can easily customize this line to define what are DB types.
let dbTypes = ThirdParty.Projects.WithNameIn("Data").ChildTypes()
.Except(ThirdParty.Types.WithNameIn("DataSet", "DataTable", "DataRow"))
from uiType in uiTypes.UsingAny(dbTypes)
let dbTypesUsed = dbTypes.Intersect(uiType.TypesUsed)
select new { uiType, dbTypesUsed }
With the CQLinq syntax it is possible to create a procedure in a query. This is useful if you wish to invoke such procedure from different locations in the query.
This possibility is illustrated in the default rule below where the procedure to check if a type can be considered as a dead type needs to be invoked from two different locations:
// <Name>Potentially dead Types</Name>
warnif count > 0
// Filter procedure for types that should'nt be considered as dead
let canTypeBeConsideredAsDeadProc = new Func<IType, bool>(
t => !t.IsPublic && // Public types might be used by client applications of your projects.
t.Name != "ClassToExclude"
// 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, t.TypesUsingMe, depth = deadTypesMetric[t] }