IndexStore

IndexStore 是一个库,它提供了一种基于查询的方法,用于在索引代码库中搜索和使用源代码符号。 它构建于 Apple IndexStoreDB 库之上,该库提供了对 swift 编译器生成的索引数据的访问。

使用此库,您可以轻松地搜索和分析代码中的符号,使其成为构建开发者工具、静态分析器和代码重构实用程序的强大工具。

注意

Apple IndexStoreDB 库尚未被认为是稳定的,因为它没有可解析的语义版本标签。 此项目指向 repo 上的一个发布分支,并被积极维护。

特性

高级主题

IndexStoreindexstore-db 之上的一个抽象层,旨在简化查询并使代码库探索更加直观。 以下是驱动其功能的关键概念

SourceSymbol

在其核心,代码中的每个不同的命名实体(例如函数、类或变量)都表示为一个符号。 当您在 IndexStore 中查询符号时,您正在寻找其主要定义和关联的元数据。

USR(统一符号解析)

将 USR 视为符号的唯一指纹。 这是一个一致的标识符,可以确保即使符号出现在不同的位置或跨项目中,我们也会知道它是同一个实体。

Occurrence

SourceSymbol 实例不仅仅是静态实体; 它们存在并贯穿于您的代码中。 当您查询符号的出现位置时,它旨在捕获项目中符号的每个特定实例或引用,从而揭示该符号在代码库中的足迹。

Related Occurrences

除了找到符号存在或出现的位置之外,通常重要的是了解其更广泛的上下文和关系。 Related Occurrences 就是这样做的,它会获取与查询符号共享定义关系的符号和实例。 这可以包括覆盖、实现、关联等,从而提供更丰富的特定符号如何与项目中其他符号交互的图景。

入门

要在您的项目中使用 IndexStore,您需要使用有效的 Configuration 实例来实例化一个索引。

Configuration 包含项目目录、libIndexStore 和 IndexStoreDB 数据库的路径等。 开始使用的唯一必需值是 projectDirectory 位置,它是您正在评估的项目的工作目录。

默认情况下,配置将根据正在运行的进程自动解析所需的 indexStorePathlibIndexStorePath。 这将使用 xcode-selectProcessInfo().environment 来获取 projectDirectory 中项目的索引存储详细信息。

您也可以通过提供自己的值来覆盖此设置。

实例化

准备好配置后,您可以创建一个 IndexStore 实例

// Manual Configuration
let configuration = Configuration(projectDirectory: "path/to/project/root")
instanceUnderTest = IndexStore(configuration: configuration)

Configuration 也是 Decodable,可以从 JSON 文件构建

let configuration = try Configuration.fromJson(at: configPath)
instanceUnderTest = IndexStore(configuration: configuration)

基本用法

配置好 IndexStore 实例后,您可以开始查询符号

  1. 导入 IndexStore
import IndexStore
  1. 使用 IndexStore 实例查询符号、出现位置或其他信息
// Query for functions by name
let results = indexStore.querySymbols(.functions("someFunctionName"))

// Find all class symbols
let classSymbols = indexStore.querySymbols(.kinds([.class]))

// Find all extensions of a type
let results = indexStore.querySymbols(.extensions(ofType: "MyClass"))

// Find all extensions of a class within specific source files
let results = indexStore.querySymbols(.extensions(in: ["path", "path"], matching: "XCTest"))

// Find all invocations of a function symbol
let function = indexStore.querySymbols(.functions("someFunctionName"))[0]
let results = indexStore.invocationsOfSymbol(function)

// Find all symbols declared in a specific file
let symbols = indexStore.querySymbols(
    .withSourceFiles(["/path/to/your/project/SourceFile.swift"])
    .withKinds(SourceKind.allCases)
    .withRoles(.all)
)

Occurrence 查找

获得符号后,无论是通过一般查询还是从其他结果中提取,您还可以通过符号(或 usr)查找出现位置。 您还可以提供有效的查询来进一步过滤结果。

// Find UIColor declaration
let colorSymbol indexStore.querySymbols(
    .withQuery("UIColor")
    .withAnchorStart(true)
    .withAnchorEnd(true)
    .withRestrictingToProjectDirectory(false)
    .withKinds([.class])
    .first!

// Look up any occurrences of the UIColor symbol
let occurrences = indexStore.queryOccurrences(ofSymbol: colorSymbol, query: .empty)

Relation 查找

获得符号后,无论是通过一般查询还是从其他结果中提取,您还可以通过符号(或 usr)查找相关的出现位置。 您还可以提供有效的查询来进一步过滤结果。

// Find UIColor declaration
let colorSymbol indexStore.querySymbols(
    .withQuery("UIColor")
    .withAnchorStart(true)
    .withAnchorEnd(true)
    .withRestrictingToProjectDirectory(false)
    .withKinds([.class])
    .first!

// Look up any occurrences of the UIColor symbol
let occurrences = indexStore.queryRelatedOccurences(ofSymbol: colorSymbol, query: .empty)

便捷方法

IndexStore 为常见的静态分析任务提供了便捷方法

let conformingSymbols = indexStore.sourceSymbols(conformingToProtocol: "SomeProtocol")
let subclassingSymbols = indexStore.sourceSymbols(subclassing: "SomeClass")
let invocations = indexStore.invocationsOfSymbol(someSymbol)
let isInvokedByTestCase = indexStore.isSymbolInvokedByTestCase(someSymbol)
let emptyExtensions = indexStore.sourceSymbols(forEmptyExtensionsMatching: "SomeType")

安装

Swift Package Manager

将以下内容添加到您的 Package.swift 文件中

let package = Package(
    // name, platforms, products, etc.
    dependencies: [
        // other dependencies
        .package(url: "https://github.com/CheekyGhost-Labs/IndexStore.git", branch: "release/3.0"),
    ],
    targets: [
        .executableTarget(name: "<command-line-tool>", dependencies: [
            // other dependencies
            .product(name: "IndexStore", package: "IndexStore")
        ]),
        // other targets
    ]
)

许可证

IndexStore 在 MIT 许可证下可用。 有关更多信息,请参阅 LICENSE 文件。

贡献

提交 Bug 报告

Swift Markdown 使用 GitHub Issues 跟踪所有错误报告。 您可以使用“IndexStore”组件来处理特定于 IndexStore 的问题和功能请求。 当您提交错误报告时,我们要求您尽可能详细地描述并包含尽可能多的信息来记录或重新创建该问题。

提交功能请求

对于功能请求,请随时提交 GitHub issue

如果您发现可以通过改进 IndexStore 来更好地满足您的需求,请不要犹豫提交功能请求。

为 IndexStore 做出贡献

由于 Apple IndexStoreDB Library repo 使用分支进行发布而不是标记稳定版本,因此 IndexStore repo 无法遵循传统的语义版本和 Git Flow 方法。

IndexStore 采用的发布方法是

发布分支将具有语义版本,但不考虑补丁更新。 例如

release/1.0
release/1.1

Apple IndexStoreDB Library 变得稳定时,发布仍将被标记。 这也将使我们能够更轻松地管理补丁版本。

在大多数情况下,应该针对 develop 分支发出拉取请求,以协调具有多个功能和修复的发布。 这还提供了一种在实际环境中使用 develop 分支进行测试的方法,以进一步测试待发布的版本。 准备好发布后,它将被合并到 main 中,并且从 main 分支创建/更新发布分支。

如果要修复旧版本,则可以针对预期的发布分支发出拉取请求,并且可以根据需要借助维护人员将更改纳入其他分支。

开始使用

  1. Fork 存储库:首先,将项目 fork 到您自己的 GitHub 帐户。

  2. 克隆 fork 的存储库:fork 后,将 fork 的存储库克隆到您的本地计算机,以便您可以进行更改。

git clone https://github.com/CheekyGhost-Labs/IndexStore.git
  1. 创建一个新分支:在进行更改之前,为您的功能或错误修复创建一个新分支。 使用一个描述性的名称来反映您的更改的目的。
git switch -c your-feature-branch
  1. 遵循 Swift 语言指南:确保您的代码遵循 Swift 语言指南,以了解样式和语法约定。

  2. 进行更改:实现您的功能或错误修复,遵循项目的代码风格和最佳实践。 不要忘记添加测试并根据需要更新文档。

  3. 提交您的更改:使用描述性和简洁的提交消息提交您的更改。 使用祈使语气,并解释您的提交所做的事情,而不是您做了什么。

# Feature
git commit -m "Feature: Adding convenience method for resolving awesomeness"


# Bug
git commit -m "Bug: Fixing issue where awesome query was not including awesome"
  1. 从上游拉取最新更改:在提交更改之前,请确保从上游存储库拉取最新更改,并将其合并到您的分支中。 这有助于避免任何潜在的合并冲突。
git pull origin develop
  1. 推送您的更改:将您的更改推送到 GitHub 上 fork 的存储库。
git push origin your-feature-branch
  1. 提交拉取请求:最后,从您 fork 的存储库到原始存储库创建一个拉取请求,以 develop 分支为目标。 在拉取请求模板中填写必要的详细信息,并等待项目维护人员审查您的贡献。

单元测试

请确保为任何更改添加单元测试。 目标不是 100% 的覆盖率,而是有意义的测试覆盖率,以确保您的更改按预期运行,而不会对现有行为产生负面影响。

请注意,项目维护人员可能会要求您对您的贡献进行更改或提供其他信息。 乐于接受反馈,并愿意根据需要进行调整。 一旦您的拉取请求获得批准并合并,您的更改将成为项目的一部分!