Terminology Overview #
以下基本术语:
- semantic node : 指代码中的语义单元,它代表了代码中的一个特定概念或实体。这些语义单元可以是函数、变量、类、接口等程序实体。Semantic Node 用于在 Kythe 中建立代码元数据的索引,使得代码的语义结构能够被有效地表示和查询。Semantic Node 通常包含以下信息:
- 名字(Name): 表示语义单元的名称,例如函数名、变量名等。
- 种类(Kind): 表示语义单元的类型,例如函数、变量、类等。
- 位置信息(Location): 表示语义单元在源代码中的位置,包括文件路径、行号、列号等。
- 其他属性(Attributes): 可能包含其他与语义单元相关的属性信息,例如访问修饰符、类型信息等。
- Anchor : 指代码中的一个位置或节点,通常对应于源代码中的一个特定位置,例如函数调用、变量引用、类定义等。Anchor 主要用于标识源代码中的关键点,并作为索引的起点,连接到其他 Kythe 节点。Anchor 具有以下特性:
- 位置信息: Anchor 包含了源代码中的位置信息,通常包括文件路径、行号、列号等,以确定具体的代码位置。
- 关联边(Edges): Anchor 通过关联边与其他 Kythe 节点建立关系。常见的关联边包括 ref、childof 等,用于表示不同节点之间的关系。
- 语义连接: Anchor 可能连接到与代码中的语义单元相关的 Semantic Node,从而帮助建立代码的语义结构。
- Edge : 是连接两个节点的关系。边承载着关于代码的语义和结构信息,构建了 Kythe 索引中的有向图。边的存在意味着两个节点之间存在某种关联或关系,这有助于在代码库中跟踪、理解和查询不同代码元素之间的连接。
- 类型(Type): 边有一个指定的类型,用于描述连接的两个节点之间的关系。例如,ref 表示引用关系,childof 表示父子关系等。这些类型的边有助于解释节点之间的语义关系。
- 起始节点和目标节点: 边连接两个节点,其中一个是起始节点,另一个是目标节点。起始节点是边的源头,目标节点是边的终点。
- 方向性: 边是有向的,即从起始节点指向目标节点。这意味着边有一个明确的方向,从一个节点到另一个节点。
- 语境信息: 边可能携带一些额外的信息,以提供关于关系的上下文。例如,一个 ref 边可能包含引用的具体位置信息,帮助定位源代码中的引用点。
Edge and Node Examples #
Jump-to-Definition #
跳转到定义允许从某个实体(如变量或函数)的用法导航到其定义。为了支持这一点,索引器至少必须发出一个定义站点锚点、一个表示被定义实体的语义节点和一个用法站点锚点。在这个示例中,我们看到了一个名为 matchRevision 的变量的一个定义和两个引用(来自 kcd.go):
func (rf *RevisionsFilter) Compile() (func(Revision) bool, error) {
if rf == nil || (rf.Revision == "" && rf.Corpus == "" && rf.Until.IsZero() && rf.Since.IsZero()) {
return func(Revision) bool { return true }, nil
}
var matchRevision, matchCorpus func(...string) bool // definition
var err error
if matchRevision, err = singleMatcher(rf.Revision); err != nil { // reference 1
return nil, err
}
if matchCorpus, err = singleMatcher(regexp.QuoteMeta(rf.Corpus)); err != nil {
return nil, err
}
return func(rev Revision) bool {
return matchRevision(rev.Revision) && // reference 2
matchCorpus(rev.Corpus) &&
(rf.Until.IsZero() || !rf.Until.Before(rev.Timestamp.In(time.UTC))) &&
(rf.Since.IsZero() || !rev.Timestamp.In(time.UTC).Before(rf.Since))
}, nil
}
matchRevision 的第一次提及(注释为 “定义”)记录了一个带有 defines/binding
边的锚,该锚指向该变量的语义节点。引用(注释为 “reference 1” 和 “reference 2”)具有指向同一语义节点的 ref
边锚。
一般来说,给定语义节点的定义站点可能不止一个,在某些情况下可能根本就没有定义。当确实存在一个唯一的定义时,我们称其为节点的 target definition。
Java 中的隐式构造函数就是一个没有定义站点的例子。如果您定义了一个类,但没有编写一个构造函数,编译器会为您编写一个构造函数,并参与交叉引用,但它在程序的源文本中没有位置。
许多 C++ 对象都有多个定义位置,包括类或函数原型的前向声明及其补全。类型(重新)声明的锚点也使用 defines/binding
边。
Callgraph #
调用图由一组三元组组成,每个三元组关联一个 call site
、一个 caller
和一个 callee
。调用点是源代码中发生调用的位置(通常是某种调用表达式);调用者是包含调用点的函数,被调用者是被调用的函数。
在 Kythe 模式中,这种结构由三个节点和两条边表示:一个位于 call site 的 anchor,以及分别代表调用者和被调用者的 semantic nodes 。调用点锚有一条指向调用者的 childof 边和一条指向被调用者的 ref/call
边:
kcd.go
func (rf *RevisionsFilter) Compile() (func(Revision) bool, error) {
memdb.go
type DB struct {
...
}
...
// Revisions implements a method of kcd.Reader.
func (db DB) Revisions(_ context.Context, want *kcd.RevisionsFilter, f func(kcd.Revision) error) error {
revisionMatches, err := want.Compile()
本图并未显示这些构造所产生的所有节点和边。特别是,完整的图将包括
memdb.go
中接口函数的覆盖(实现)的额外节点。为简洁起见,我们省略了该图的分支。
这里我们还有 kcd.go 的两个部分,围绕着 Compile 函数的使用。该函数在 kcd.go 中定义,然后在 memdb.go 中通过函数调用引用。在 kcd.go 中,Compile 的代码锚有一个 childof 边指向 Revisions 的函数语义节点。
Class/Interface Hierarchy & Overrides #
继承关系由 extends 和 satisfies 边沿捕捉。覆盖关系由 overrides 边表示。Go 的这个示例对这些关系进行了说明(该示例使用 satisfies 边来表达 Go 的隐式接口满足关系)。在这个示例中,childof 边捕捉了方法及其类型之间的包含关系:
kcd.go
type Reader interface {
Revisions(_ context.Context, filter *RevisionsFilter, f func(Revision) error) error
}
memdb.go
type DB struct {
...
}
...
// Revisions implements a method of kcd.Reader.
func (db DB) Revisions(_ context.Context, want *kcd.RevisionsFilter, f func(kcd.Revision) error) error {
这里我们再次看到 kcd.go 中的顶级阅读器接口。同一文件中的接口函数 Revisions 有一条 childof 边指向该接口的节点。memdb.go中的Reader实现有一条satisfies边指向父接口,而它自己的Revisions函数有一条overrides边指向kcd.go中的顶层Revisions函数。对于 C++ 和 Java,接口、抽象类等在 Kythe 模式中也有类似的关系。
对于明确声明继承和接口实现的语言(如 Java),Kythe 使用 extends 边标签而不是 satisfies 边标签。