TypeScript 的 CodeQL 库¶
当您分析 TypeScript 程序时,您可以利用 TypeScript 的 CodeQL 库中大量类。
概述¶
对分析 TypeScript 代码的支持与 JavaScript 的 CodeQL 库捆绑在一起,因此您可以通过导入 javascript.qll
模块来包含完整的 TypeScript 库。
import javascript
JavaScript 的 CodeQL 库涵盖了此库的大部分内容,并且与 TypeScript 分析也相关。本文档以 TypeScript 特定的类和谓词补充了 JavaScript 文档。
语法¶
TypeScript 中的大多数语法以与 JavaScript 对应语法相同的方式表示。例如,a+b
由 AddExpr 表示;与 JavaScript 中一样。另一方面,x as number
由 TypeAssertion 表示,该类是 TypeScript 特定的类。
类型注释¶
TypeExpr 类表示类型注释的一部分。
只有源代码中明确的类型注释才会作为 TypeExpr
出现。TypeScript 编译器推断的类型是 Type
实体;有关此方面的详细信息,请参阅关于 静态类型信息 的部分。
有几种方法可以访问类型注释,例如
VariableDeclaration.getTypeAnnotation()
Function.getReturnTypeAnnotation()
BindingPattern.getTypeAnnotation()
Parameter.getTypeAnnotation()
(BindingPattern.getTypeAnnotation()
的特例)VarDecl.getTypeAnnotation()
(BindingPattern.getTypeAnnotation()
的特例)FieldDeclaration.getTypeAnnotation()
TypeExpr 类提供了一些方便的成员谓词,如 isString()
和 isVoid()
来识别常用的类型。
表示类型注释的子类为
- TypeAccess:引用类型的名称,如
Date
或http.ServerRequest
。- LocalTypeAccess:未限定的名称,如
Date
。 - QualifiedTypeAccess:以命名空间为前缀的名称,如
http.ServerRequest
。 - ImportTypeAccess:用作类型的
import
,如import("./foo")
。
- LocalTypeAccess:未限定的名称,如
- PredefinedTypeExpr:预定义类型,如
number
、string
、void
或any
。 - ThisTypeExpr:
this
类型。 - InterfaceTypeExpr,也称为文字类型,如
{x: number}
。 - FunctionTypeExpr:
(x: number) => string
之类的类型。 - GenericTypeExpr:具有类型参数的命名类型,如
Array<string>
。 - LiteralTypeExpr:用作类型的字符串、数字或布尔常量,如
'foo'
。 - ArrayTypeExpr:
string[]
之类的类型。 - UnionTypeExpr:
string | number
之类的类型。 - IntersectionTypeExpr:
S & T
之类的类型。 - IndexedAccessTypeExpr:
T[K]
之类的类型。 - ParenthesizedTypeExpr:
(string)
之类的类型。 - TupleTypeExpr:
[string, number]
之类的类型。 - KeyofTypeExpr:
keyof T
之类的类型。 - TypeofTypeExpr:
typeof x
之类的类型。 - IsTypeExpr:
x is string
之类的类型。 - MappedTypeExpr:
{ [K in C]: T }
之类的类型。
还有一些子类可能是类型注释的一部分,但它们本身不是类型。
- TypeParameter:在类型或函数上声明的类型参数,如
class C<T> {}
中的T
。 - NamespaceAccess:从类型内部引用命名空间的名称,如
http.ServerRequest
中的http
。- LocalNamespaceAccess:前缀中的初始标识符,如
http.ServerRequest
中的http
。 - QualifiedNamespaceAccess:前缀中的限定名称,如
net.client.Connection
中的net.client
。 - ImportNamespaceAccess:用作类型中命名空间的
import
,如import("http").ServerRequest
中那样。
- LocalNamespaceAccess:前缀中的初始标识符,如
- VarTypeAccess:对类型内部值的引用,如
typeof x
或x is string
中的x
。
函数签名¶
Function 类是一个广泛的类,它既包括具体的函数,也包括函数签名。
函数签名可以采取多种形式
- 函数类型,如
(x: number) => string
。 - 抽象方法,如
abstract foo(): void
。 - 重载签名,如
foo(x: number): number
后跟foo
的实现。 - 调用签名,如
{ (x: string): number }
中那样。 - 索引签名,如
{ [x: string]: number }
中那样。 - 处于环境上下文中的函数,如
declare function foo(x: number): string
。
建议您使用谓词 Function.hasBody()
来区分具体的函数和签名。
类型参数¶
TypeParameter 类表示类型参数,而 TypeParameterized 类表示可以声明类型参数的实体。类、接口、类型别名、函数和映射类型表达式都是 TypeParameterized
。
您可以使用以下谓词访问类型参数
TypeParameterized.getTypeParameter(n)
获取第n
个声明的类型参数。TypeParameter.getHost()
获取声明给定类型参数的实体。
您可以使用以下谓词访问类型参数
GenericTypeExpr.getTypeArgument(n)
获取类型的第n
个类型参数。TypeAccess.getTypeArgument(n)
是上述内容的便捷替代方法(具有类型参数的 TypeAccess 包含在 GenericTypeExpr 中)。InvokeExpr.getTypeArgument(n)
获取调用的第n
个类型参数。ExpressionWithTypeArguments.getTypeArgument(n)
获取泛型超类表达式的第n
个类型参数。
要选择对给定类型参数的引用,请使用 getLocalTypeName()
(请参阅下面的 名称绑定)。
示例¶
选择将值强制转换为类型参数的表达式
import javascript
from TypeParameter param, TypeAssertion assertion
where assertion.getTypeAnnotation() = param.getLocalTypeName().getAnAccess()
select assertion, "Cast to type parameter."
类和接口¶
CodeQL 类 ClassOrInterface 是类和接口的通用超类型,并提供一些 TypeScript 特定的成员谓词
ClassOrInterface.isAbstract()
在此为接口或具有abstract
修饰符的类时成立。ClassOrInterface.getASuperInterface()
从类的implements
子句或接口的extends
子句获取类型。ClassOrInterface.getACallSignature()
获取接口的调用签名,例如在{ (arg: string): number }
中。ClassOrInterface.getAnIndexSignature()
获取索引签名,例如在{ [key: string]: number }
中。ClassOrInterface.getATypeParameter()
获取声明的类型参数(TypeParameterized.getATypeParameter()
的特殊情况)。
请注意,类的超类是一个表达式,而不是类型注释。如果超类具有类型参数,它将是 ExpressionWithTypeArguments 类型的表达式。
另请参阅“CodeQL 库 for JavaScript”中的类文档。
要选择对类或接口的类型引用,请使用 getTypeName()
。
语句¶
以下是 TypeScript 特定的语句
- NamespaceDeclaration:如
namespace M {}
的语句。 - EnumDeclaration:如
enum Color { red, green, blue }
的语句。 - TypeAliasDeclaration:如
type A = number
的语句。 - InterfaceDeclaration:如
interface Point { x: number; y: number; }
的语句。 - ImportEqualsDeclaration:如
import fs = require("fs")
的语句。 - ExportAssignDeclaration:如
export = M
的语句。 - ExportAsNamespaceDeclaration:如
export as namespace M
的语句。 - ExternalModuleDeclaration:如
module "foo" {}
的语句。 - GlobalAugmentationDeclaration:如
global {}
的语句
表达式¶
以下是 TypeScript 特定的表达式
- ExpressionWithTypeArguments:当类的
extends
子句具有类型参数时出现,例如在class C extends D<string>
中。 - TypeAssertion:断言值为给定类型,例如
x as number
或<number> x
。 - NonNullAssertion:断言值不为空或未定义,例如
x!
。 - ExternalModuleReference:导入分配右侧的
require
调用,例如import fs = require("fs")
。
环境声明¶
类型注释、接口和类型别名被认为是环境 AST 节点,任何具有 declare
修饰符的节点也是如此。
谓词 ASTNode.isAmbient()
可用于确定 AST 节点是否为环境节点。
环境节点主要被控制流和数据流分析忽略。环境声明的最外层在控制流图中具有单个无操作节点,并且没有内部控制流。
静态类型信息¶
静态类型信息和全局名称绑定可用于启用了“完整”TypeScript 提取的项目。此选项在使用 CodeQL CLI 创建数据库时默认启用。
基本用法¶
Type 类表示静态类型,例如 number
或 string
。可以使用 Expr.getType()
获取表达式的类型。
可以通过多种方式识别引用特定命名类型的类型
type.(TypeReference).hasQualifiedName(name)
在类型引用给定命名类型时成立。type.(TypeReference).hasUnderlyingType(name)
在类型引用给定命名类型或其传递子类型时成立。type.hasUnderlyingType(name)
与上面类似,但还适用于引用包含在联合类型和/或交叉类型中时。
hasQualifiedName
和 hasUnderlyingType
谓词有两种重载
- 单参数版本采用相对于全局范围的限定名称。
- 双参数版本采用模块的名称和相对于该模块的限定名称。
示例¶
以下查询可用于查找对 Node.js Buffer
对象的所有 toString
调用
import javascript
from MethodCallExpr call
where call.getReceiver().getType().hasUnderlyingType("Buffer")
and call.getMethodName() = "toString"
select call
使用类型¶
Type
实体不与特定源位置相关联。例如,number
关键字可能有多种用法,但只有一个 number
类型。
Type
的一些重要成员谓词是
Type.getProperty(name)
获取命名属性的类型。Type.getMethod(name)
获取命名方法的签名。Type.getSignature(kind,n)
获取调用或构造函数签名的第n
个重载。Type.getStringIndexType()
获取字符串索引签名的类型。Type.getNumberIndexType()
获取数字索引签名的类型。
Type
实体始终属于以下子类之一
TypeReference
:命名类型,可能具有类型参数。UnionType
:联合类型,例如string | number
。IntersectionType
:交叉类型,例如T & U
。TupleType
: 元组类型,例如[string, number]
。StringType
:string
类型。NumberType
:number
类型。AnyType
:any
类型。NeverType
:never
类型。VoidType
:void
类型。NullType
:null
类型。UndefinedType
:undefined
类型。ObjectKeywordType
:object
类型。SymbolType
:symbol
或unique symbol
类型。AnonymousInterfaceType
: 匿名类型,例如{x: number}
。TypeVariableType
: 对类型变量的引用。ThisType
: 特定类型中的this
类型。TypeofType
: 具名值的类型,例如typeof X
。BooleanLiteralType
:true
或false
类型。StringLiteralType
: 字符串常量的类型。NumberLiteralType
: 数字常量的类型。
此外,Type
还有以下子类,它们与上述子类部分重叠。
BooleanType
:boolean
类型,在内部表示为联合类型true | false
。PromiseType
: 描述承诺的类型,例如Promise<T>
。ArrayType
: 描述数组对象的类型,可能是元组类型。PlainArrayType
: 形式为Array<T>
的类型。ReadonlyArrayType
: 形式为ReadonlyArray<T>
的类型。
LiteralType
: 布尔值、字符串或数字字面量类型。NumberLikeType
:number
类型或数字字面量类型。StringLikeType
:string
类型或字符串字面量类型。BooleanLikeType
:true
、false
或boolean
类型。
规范名称和具名类型¶
CanonicalName
是一个 CodeQL 类,它表示相对于根范围(例如模块或全局范围)的限定名称。它通常代表一个实体,例如类型、命名空间、变量或函数。 TypeName
和 Namespace
是此类的子类。
可以使用 hasQualifiedName
谓词识别规范名称。
hasQualifiedName(name)
如果限定名称相对于全局范围为name
则成立。hasQualifiedName(module,name)
如果限定名称相对于给定模块名称为name
则成立。
为方便起见,此谓词在其他类(例如 TypeReference
和 TypeofType
)中也可用,它转发到底层规范名称。
函数类型¶
没有用于函数类型的 CodeQL 类,因为任何具有调用或构造签名类型的函数都可用作函数。类型 CallSignatureType
表示此类签名(带或不带 new
关键字)。
可以通过几种方式获得签名。
Type.getFunctionSignature(n)
获取第n
个重载函数签名。Type.getConstructorSignature(n)
获取第n
个重载构造函数签名。Type.getLastFunctionSignature()
获取最后声明的函数签名。Type.getLastConstructorSignature()
获取最后声明的构造函数签名。
CallSignatureType
的一些重要成员谓词是
CallSignatureType.getParameter(n)
获取第n
个参数的类型。CallSignatureType.getParameterName(n)
获取第n
个参数的名称。CallSignatureType.getReturnType()
获取返回值类型。
请注意,签名与特定声明位置无关。
调用解析¶
对于调用表达式,有其他类型信息可用。
InvokeExpr.getResolvedCallee()
获取被调用者作为具体的Function
。InvokeExpr.getResolvedCalleeName()
获取被调用者作为规范名称。InvokeExpr.getResolvedSignature()
获取被调用函数的签名,其中重载已解析,类型参数已替换。
请注意,这些引用由类型系统确定的调用目标。实际调用目标可能在运行时不同,例如,如果目标是已在子类中覆盖的方法。
继承和子类型化¶
可以使用 TypeName.getABaseTypeName()
获取具名类型的声明超类型。
这在类型名称级别进行操作,因此继承链中使用的特定类型参数不可用。但是,这些通常可以使用 Type.getProperty
或 Type.getMethod
推断出来,它们都考虑了继承。
这仅考虑在类型的 extends
或 implements
子句中显式提及的类型。没有谓词可以确定一般情况下类型之间的子类型化或可赋值性。
以下两个谓词可用于识别给定类型的子类型。
Type.unfold()
展开联合和/或交集类型并获取底层类型,如果它不是联合或交集,则获取类型本身。Type.hasUnderlyingType(name)
如果类型是给定具名类型的引用,则成立,可能是在展开联合/交集并遵循声明的超类型之后。
示例¶
以下查询可用于查找所有作为 React 组件的类,以及它们的 props
属性的类型,这通常与其第一个类型参数一致。
import javascript
from ClassDefinition cls, TypeName name
where name = cls.getTypeName()
and name.getABaseTypeName+().hasQualifiedName("React.Component")
select cls, name.getType().getProperty("props")
名称绑定¶
在 TypeScript 中,名称可以引用变量、类型和命名空间,或者这些的组合。
这些概念被建模为不同的实体:Variable、TypeName 和 Namespace。例如,下面的类 C
引入了变量和类型。
class C {}
let x = C; // refers to the variable C
let y: C; // refers to the type C
变量 C
和类型 C
被建模为不同的实体。一个是 Variable,另一个是 TypeName。
TypeScript 还允许您导入类型和命名空间,并在不同的范围内为它们提供本地名称。例如,下面的导入引入了本地类型名称 B
。
import {C as B} from "./foo"
本地名称 B
表示为一个名为 B
的 LocalTypeName,它仅限于包含导入的文件。导入语句还可以引入 Variable 和 LocalNamespaceName。
下表显示了用于处理每种名称的相关类。这些类将在下面详细描述。
种类 | 本地别名 | 规范名称 | 定义 | 访问 |
---|---|---|---|---|
值 | 变量 | VarAccess | ||
类型 | LocalTypeName | TypeName | TypeDefinition | TypeAccess |
命名空间 | LocalNamespaceName | 命名空间 | NamespaceDefinition | NamespaceAccess |
注意: TypeName
和 Namespace
仅在使用完整的 TypeScript 提取生成数据库时才填充。 LocalTypeName
和 LocalNamespaceName
始终填充。
类型名称¶
一个 TypeName 是对类型的限定名称,不绑定到特定词法范围。 TypeDefinition 类表示定义类型的实体,即类、接口、类型别名、枚举或枚举成员。 用于处理类型名的相关谓词是
TypeAccess.getTypeName()
获取引用的限定名称(如果有)。TypeDefinition.getTypeName()
获取类、接口、类型别名、枚举或枚举成员的限定名称。TypeName.getAnAccess()
获取对给定类型的访问。TypeName.getADefinition()
获取给定类型的定义。 注意,接口可以有多个定义。
一个 LocalTypeName 的行为类似于块级作用域变量,即它具有非限定名称,并且仅限于特定范围。 相关谓词是
LocalTypeAccess.getLocalTypeName()
获取非限定类型访问引用的本地名称。LocalTypeName.getAnAccess()
获取对本地类型名的访问。LocalTypeName.getADeclaration()
获取此名称的声明。LocalTypeName.getTypeName()
获取此名称所指的限定名称。
示例¶
查找省略对泛型类型的类型参数的引用。
最好使用 TypeName 来解析导入和限定名称
import javascript
from TypeDefinition def, TypeAccess access
where access.getTypeName().getADefinition() = def
and def.(TypeParameterized).hasTypeParameters()
and not access.hasTypeArguments()
select access, "Type arguments are omitted"
查找用作类型和值的导入名称
import javascript
from ImportSpecifier spec
where exists (LocalTypeAccess access | access.getLocalTypeName().getADeclaration() = spec.getLocal())
and exists (VarAccess access | access.getVariable().getADeclaration() = spec.getLocal())
select spec, "Used as both variable and type"
命名空间名称¶
命名空间由 Namespace 和 LocalNamespaceName 类表示。 NamespaceDefinition 类表示命名空间的语法定义,包括普通命名空间声明和枚举声明。
注意,这些类只处理从类型注释中引用的命名空间,而不是通过表达式引用的命名空间。
一个 Namespace 是命名空间的限定名称,不绑定到特定范围。 用于处理命名空间的相关谓词是
NamespaceAccess.getNamespace()
获取命名空间访问引用的命名空间。NamespaceDefinition.getNamespace()
获取由命名空间或枚举声明定义的命名空间。Namespace.getAnAccess()
获取对类型内部的命名空间的访问。Namespace.getADefinition()
获取此命名空间的定义。 注意,命名空间可以有多个定义。Namespace.getNamespaceMember(name)
获取具有给定名称的内部命名空间。Namespace.getTypeMember(name)
获取以给定名称导出的类型。Namespace.getAnExportingContainer()
获取一个 StmtContainer,其导出内容会贡献到此命名空间。 这可以是命名空间声明的主体或模块的顶层。 枚举没有导出容器。
一个 LocalNamespaceName 的行为类似于块级作用域变量,即它具有非限定名称,并且仅限于特定范围。 相关谓词是
LocalNamespaceAccess.getLocalNamespaceName()
获取标识符引用的本地名称。LocalNamespaceName.getAnAccess()
获取引用此本地名称的标识符。LocalNamespaceName.getADeclaration()
获取声明此本地名称的标识符。LocalNamespaceName.getNamespace()
获取此名称所指的命名空间。