CodeQL 文档

TypeScript 的 CodeQL 库

当您分析 TypeScript 程序时,您可以利用 TypeScript 的 CodeQL 库中大量类。

概述

对分析 TypeScript 代码的支持与 JavaScript 的 CodeQL 库捆绑在一起,因此您可以通过导入 javascript.qll 模块来包含完整的 TypeScript 库。

import javascript

JavaScript 的 CodeQL 库涵盖了此库的大部分内容,并且与 TypeScript 分析也相关。本文档以 TypeScript 特定的类和谓词补充了 JavaScript 文档。

语法

TypeScript 中的大多数语法以与 JavaScript 对应语法相同的方式表示。例如,a+bAddExpr 表示;与 JavaScript 中一样。另一方面,x as numberTypeAssertion 表示,该类是 TypeScript 特定的类。

类型注释

TypeExpr 类表示类型注释的一部分。

只有源代码中明确的类型注释才会作为 TypeExpr 出现。TypeScript 编译器推断的类型是 Type 实体;有关此方面的详细信息,请参阅关于 静态类型信息 的部分。

有几种方法可以访问类型注释,例如

  • VariableDeclaration.getTypeAnnotation()
  • Function.getReturnTypeAnnotation()
  • BindingPattern.getTypeAnnotation()
  • Parameter.getTypeAnnotation()BindingPattern.getTypeAnnotation() 的特例)
  • VarDecl.getTypeAnnotation()BindingPattern.getTypeAnnotation() 的特例)
  • FieldDeclaration.getTypeAnnotation()

TypeExpr 类提供了一些方便的成员谓词,如 isString()isVoid() 来识别常用的类型。

表示类型注释的子类为

还有一些子类可能是类型注释的一部分,但它们本身不是类型。

  • TypeParameter:在类型或函数上声明的类型参数,如 class C<T> {} 中的 T
  • NamespaceAccess:从类型内部引用命名空间的名称,如 http.ServerRequest 中的 http
  • VarTypeAccess:对类型内部值的引用,如 typeof xx 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 特定的语句

表达式

以下是 TypeScript 特定的表达式

环境声明

类型注释、接口和类型别名被认为是环境 AST 节点,任何具有 declare 修饰符的节点也是如此。

谓词 ASTNode.isAmbient() 可用于确定 AST 节点是否为环境节点。

环境节点主要被控制流和数据流分析忽略。环境声明的最外层在控制流图中具有单个无操作节点,并且没有内部控制流。

静态类型信息

静态类型信息和全局名称绑定可用于启用了“完整”TypeScript 提取的项目。此选项在使用 CodeQL CLI 创建数据库时默认启用。

基本用法

Type 类表示静态类型,例如 numberstring。可以使用 Expr.getType() 获取表达式的类型。

可以通过多种方式识别引用特定命名类型的类型

  • type.(TypeReference).hasQualifiedName(name) 在类型引用给定命名类型时成立。
  • type.(TypeReference).hasUnderlyingType(name) 在类型引用给定命名类型或其传递子类型时成立。
  • type.hasUnderlyingType(name) 与上面类似,但还适用于引用包含在联合类型和/或交叉类型中时。

hasQualifiedNamehasUnderlyingType 谓词有两种重载

  • 单参数版本采用相对于全局范围的限定名称。
  • 双参数版本采用模块的名称和相对于该模块的限定名称。

示例

以下查询可用于查找对 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: symbolunique symbol 类型。
  • AnonymousInterfaceType: 匿名类型,例如 {x: number}
  • TypeVariableType: 对类型变量的引用。
  • ThisType: 特定类型中的 this 类型。
  • TypeofType: 具名值的类型,例如 typeof X
  • BooleanLiteralType: truefalse 类型。
  • StringLiteralType: 字符串常量的类型。
  • NumberLiteralType: 数字常量的类型。

此外,Type 还有以下子类,它们与上述子类部分重叠。

  • BooleanType: boolean 类型,在内部表示为联合类型 true | false
  • PromiseType: 描述承诺的类型,例如 Promise<T>
  • ArrayType: 描述数组对象的类型,可能是元组类型。
    • PlainArrayType: 形式为 Array<T> 的类型。
    • ReadonlyArrayType: 形式为 ReadonlyArray<T> 的类型。
  • LiteralType: 布尔值、字符串或数字字面量类型。
  • NumberLikeType: number 类型或数字字面量类型。
  • StringLikeType: string 类型或字符串字面量类型。
  • BooleanLikeType: truefalseboolean 类型。

规范名称和具名类型

CanonicalName 是一个 CodeQL 类,它表示相对于根范围(例如模块或全局范围)的限定名称。它通常代表一个实体,例如类型、命名空间、变量或函数。 TypeNameNamespace 是此类的子类。

可以使用 hasQualifiedName 谓词识别规范名称。

  • hasQualifiedName(name) 如果限定名称相对于全局范围为 name 则成立。
  • hasQualifiedName(module,name) 如果限定名称相对于给定模块名称为 name 则成立。

为方便起见,此谓词在其他类(例如 TypeReferenceTypeofType)中也可用,它转发到底层规范名称。

函数类型

没有用于函数类型的 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.getPropertyType.getMethod 推断出来,它们都考虑了继承。

这仅考虑在类型的 extendsimplements 子句中显式提及的类型。没有谓词可以确定一般情况下类型之间的子类型化或可赋值性。

以下两个谓词可用于识别给定类型的子类型。

  • 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 中,名称可以引用变量、类型和命名空间,或者这些的组合。

这些概念被建模为不同的实体:VariableTypeNameNamespace。例如,下面的类 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 表示为一个名为 BLocalTypeName,它仅限于包含导入的文件。导入语句还可以引入 VariableLocalNamespaceName

下表显示了用于处理每种名称的相关类。这些类将在下面详细描述。

种类 本地别名 规范名称 定义 访问
变量     VarAccess
类型 LocalTypeName TypeName TypeDefinition TypeAccess
命名空间 LocalNamespaceName 命名空间 NamespaceDefinition NamespaceAccess

注意: TypeNameNamespace 仅在使用完整的 TypeScript 提取生成数据库时才填充。 LocalTypeNameLocalNamespaceName 始终填充。

类型名称

一个 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"

命名空间名称

命名空间由 NamespaceLocalNamespaceName 类表示。 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() 获取此名称所指的命名空间。
  • ©GitHub, Inc.
  • 条款
  • 隐私