首页/文档/PRQL 与代码查询/用CppDepend掌握PRQL语法

用CppDepend掌握PRQL语法

用CppDepend掌握PRQL语法

本文档假定您熟悉 LINQ语法,并展示PRQL语法的特点。

PRQL(Code Query LINQ)是 CppDepend 工具自版本 3 起提供的功能,用于通过 LINQ 查询查询 C/C++ 代码。

入门视频

CppDepend 文档网站上有一个介绍 PRQL 语法的 1 分 50 秒短视频。

PRQL查询编辑

PRQL 查询可以在 CppDepend UI 中实时编辑(独立版或 Visual Studio 中)。

PRQL查询编辑器

查询编译成功后立即自动执行。

请注意上面截图中的 13毫秒 位于右上方,指示查询的执行时长。

PRQL 速度很快,专为针对大型真实代码库每秒运行数百个查询而设计。

PRQL版还附带 代码补全/IntelliSense,以及 工具提示文档 鼠标悬停在查询体上时。

预定义域

PRQL 定义了一些可查询的预定义域,包括: 类型 ; 方法 ; 字段 ; 命名空间 ; 项目

这些域不仅枚举所查询代码库的所有代码元素,还枚举所有 第三方 代码库使用的代码元素(例如类型 string 以及该类型的所有方法和字段 string 被代码库使用的)。

语法如下简单:

1from m in Methods where m.NbLinesOfCode > 30 select m

PRQL 查询可以依赖一个或多个域。请注意上面查询中域词如何 方法 以不同方式高亮。

有两个常用且方便的预定义域: 应用程序ThirdParty。顾名思义,这些域可用于枚举仅在应用程序项目中定义的代码元素,或仅在第三方项目中定义的代码元素(如 STL, MFCBoost) 被应用程序代码使用。这两个域分别代表整个代码库的部分视图。

1from m in Application.Methods where m.NbLinesOfCode > 30 select m

细化这些预定义域很容易。

例如,下面的查询匹配定义的大型方法 在命名空间中 ProductName.FeatureA 及其子命名空间:

1from m in Application.Namespaces.WithNameLike("ProductName.FeatureA").ChildMethods()
2where m.CyclomaticComplexity > 10 select m

使用 notmycode 前缀定义代码库视图 JustMyCode

还有另一个方便的预定义域,名为 JustMyCode.

JustMyCode 代表 PRQL 的一项功能,用于从 PRQL 查询结果中排除生成的代码元素。

例如,以下查询只匹配不是由工具(如 UI 设计器)生成的大型方法:

1from m in JustMyCode.Methods where m.NbLinesOfCode > 30 select m

生成的代码元素集合由以 PRQL 关键字为前缀的 PRQL 查询定义 notmycode.

例如,下面的查询匹配在名称包含特定单词的源文件中定义的方法。

1notmycode from m in Methods where
2
3 m.SourceFileDeclAvailable &&
4
5 m.SourceDecls.First().SourceFile.FileName.ToLower().Contains("generated.cpp")
6
7select m

PRQL查询运行器执行所有 notmycode 依赖于的查询之前的查询 JustMyCode,因此该域 JustMyCode 只定义一次。显然,如果出现以下情况,PRQL 编译器会报错 notmycode 查询依赖于 JustMyCode 域。

PRQL代码规则

通过在 PRQL 查询前添加使用两个 PRQL 关键字定义的条件,可以轻松将其转换为规则 warnif计数.

关键字 count 是一个无符号整数,等于查询匹配的代码元素数量。

例如,如果代码库的应用程序方法中匹配到大型方法,以下查询会发出警告:

1// <Name>Avoid too large methods</Name>
2
3warnif count > 0
4
5from m in Application.Methods
6
7where m.NbLinesOfCode > 30
8
9select m
查询编辑器中的警告标签

PRQL 代码规则可用于定义团队希望在代码库中避免的不良实践。

团队可以在 CppDepend UI(独立版或 Visual Studio 中)中看到代码规则违规警告,或者 在报告中.

团队还可以将某些规则定义为 严重规则.

查询运算符和查询表达式语法

使用 PRQL 时,查询运算符语法和查询表达式语法都允许使用。

查询运算符语法是直接调用如下方法的语法 Where()Select()...

1Methods.Where(m => m.NbLinesOfCode > 30)

查询表达式语法包含如下关键字 where选择...

1from m in Methods where m.NbLinesOfCode > 30 select m

通常,在一个查询中混合使用两种语法会很方便。

  • 查询运算符语法便于定义子集和子域。
  • 查询表达式语法便于在这些子集和子域上定义操作。

例如,下面的查询使用查询运算符语法定义静态类型中定义的方法子集,并使用查询表达式语法从该子集中筛选和投影大型方法。

1from m in Application.Types.Where(t => t.IsNested).ChildMethods()
2where m.NbLinesOfCode > 30 select m

用let定义范围变量

LINQ 语法提供了使用关键字定义范围变量的功能 在本节中,我们想强调这种可能性,因为使用 关键字是编写 PRQL 查询时的常见做法。

例如,以下默认规则借助多个范围变量定义了一个自定义代码指标:

1// <Name>C.R.A.P method code metric</Name>
2
3// Change Risk Analyzer and Predictor (i.e. CRAP) code metric
4
5// This code metric helps in pinpointing overly complex and untested code.
6
7// Formula: CRAP(m) = comp(m)^2 * (1 - cov(m)/100)^3 + comp(m)
8
9warnif count > 0
10
11from m in JustMyCode.Methods
12
13// Don&apos;t match too short methods
14
15where m.NbLinesOfCode > 10
16
17let CC = m.CyclomaticComplexity
18
19let uncov = (100 - m.PercentageCoverage) / 100f
20
21let CRAP = (CC * CC * uncov * uncov * uncov) + CC
22
23where CRAP != null && CRAP > 30
24
25orderby CRAP descending, m.NbLinesOfCode descending
26
27select new { m, CRAP, CC, uncoveredPercentage = uncov*100, m.NbLinesOfCode }

请注意使用许多 主查询循环中的子句,可能会显著降低查询执行性能。

以let开始查询

PRQL 编译器扩展了 LINQ 的用法 关键字,因为使用PRQL, 关键字可用于在 PRQL 查询开头定义变量。

例如,下面的默认 PRQL 规则首先尝试匹配 BaseClass 类型;如果找到,则执行查询。

1warnif count > 0
2
3let base = ThirdParty.Types.WithFullName("BaseClass").FirstOrDefault()
4
5where base != null // base can be null if the code base doesn&apos;t use at all BaseClass
6
7from t in Application.Types where
8
9 !t.DeriveFrom(base) select t

对于其他一些 PRQL 规则,通过多个以下方式定义多个子集可能很方便 关键字表达式,然后再执行查询本身。

例如,下面的默认 PRQL 规则首先定义子集 uiTypesdbTypes 在查询代码中使用之前。

1// <Name>UI layer shouldn&apos;t use directly DB types</Name>
2
3warnif count > 0
4
5// UI layer is made of types in namespaces using a UI framework
6
7let uiTypes = Application.Namespaces.UsingAny(
8
9 Projects.WithNameIn("PresentationFramework", "MFC")
10
11 ).ChildTypes()
12
13// You can easily customize this line to define what are DB types.
14
15let dbTypes = ThirdParty.Assemblies.WithNameIn("Data").ChildTypes()
16
17 .Except(ThirdParty.Types.WithNameIn("DataSet", "DataTable", "DataRow"))
18
19from uiType in uiTypes.UsingAny(dbTypes)
20
21let dbTypesUsed = dbTypes.Intersect(uiType.TypesUsed)
22
23select new { uiType, dbTypesUsed }

在查询中定义过程

使用 LINQ 语法,可以在查询中创建过程。如果您想从查询中的不同位置调用该过程,这将非常有用。

下面的默认规则演示了这种可能性,其中检查某个类型是否 可被视为死类型 需要从两个不同的位置调用:

1// <Name>Potentially dead Types</Name>
2
3warnif count > 0
4
5// Filter procedure for types that shouldn&apos;t be considered as dead
6
7let canTypeBeConsideredAsDeadProc = new Func<IType, bool>(
8
9 t => !t.IsPublic && // Public types might be used by client applications of your assemblies.
10
11 t.Name != "ClassToExclude"
12
13// Select types unused
14
15let typesUnused =
16
17 from t in JustMyCode.Types where
18
19 t.NbTypesUsingMe == 0 && canTypeBeConsideredAsDeadProc(t)
20
21 select t
22
23// Dead types = types used only by unused types (recursive)
24
25let deadTypesMetric = typesUnused.FillIterative(
26
27types => from t in codeBase.Application.Types.UsedByAny(types).Except(types)
28
29 where canTypeBeConsideredAsDeadProc(t) &&
30
31 t.TypesUsingMe.Intersect(types).Count() == t.NbTypesUsingMe
32
33 select t)
34
35from t in deadTypesMetric.DefinitionDomain
36
37select new { t, t.TypesUsingMe, depth = deadTypesMetric[t] }

立即试用 CppDepend

开始 14 天免费试用,畅享全部文档功能。无需信用卡。