使用 @posthog/enricher 检测与丰富 PostHog SDK 用法:基于 tree-sitter 的源码静态分析实战指南
使用 posthog/enricher 检测与丰富 PostHog SDK 用法基于 tree-sitter 的源码静态分析实战指南【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthogposthog/enricher 是 PostHog 桌面应用products/desktop中负责检测 丰富PostHog SDK 用法的核心库它使用 tree-sitter AST 分析在 JavaScript、TypeScript、JSX、TSX、Python、Go、Ruby 源码中定位capture()调用、feature flag 检查、init()调用与 variant 分支再通过 PostHog API 把每个用法与项目中的 feature flag、实验、事件定义及事件量级统计数据关联起来。读完本文你将掌握 enricher 的完整 APIPostHogEnricher/ParseResult/EnrichedResult、底层PostHogDetector检测 API、flag 类型与陈旧度分类逻辑以及如何为源码生成带注释标注的自解释版本。一、enricher 能做什么从看到代码到读懂代码posthog/enricher对外暴露两条主线能力静态检测Detection在不运行代码的前提下通过 tree-sitter 语法树识别源码中所有与 PostHog SDK 相关的调用点——capture()事件上报、getFeatureFlag()/isFeatureEnabled()等 flag 检查、posthog.init()初始化调用、flag 赋值语句以及多变量 flag 的 if/switch 分支。API 丰富Enrichment以检测结果为入参调用 PostHog 的公开 API通过项目 API Key为每个事件和 flag 补充真实项目上下文——flag 的活跃状态、发布比例、所属实验、陈旧度事件是否已验证、最近出现时间、标签与调用量/独立用户数。两者叠加之后开发者或基于它的 Agent无需在 IDE 与 PostHog 控制台之间来回切换就能一眼看出这个capture(purchase)对应已验证事件、近 30 天约 1.25 万次调用或这个getFeatureFlag(new-checkout)引用的 flag 已经 100% 发布、处于 stale 状态。从仓库布局看该包位于 products/desktop/packages/enricher核心实现分布在src/目录下的 enricher.ts入口门面、detector.ts低层检测门面、languages.ts各语言的 tree-sitter 查询与方法集合以及 enrich-source.ts共享的丰富流水线等文件中。二、快速开始安装并导入后核心用法只有三步创建实例 → 解析源码 → 读取结果。import { PostHogEnricher } from posthog/enricher; const enricher new PostHogEnricher(); // 从源码字符串解析显式指定语言 ID const result await enricher.parse(sourceCode, typescript); // 或者直接从文件解析根据扩展名自动识别语言 const result await enricher.parseFile(/path/to/app.tsx); result.events; // [{ name: purchase, line: 5, dynamic: false }] result.flagChecks; // [{ method: getFeatureFlag, flagKey: new-checkout, line: 8 }] result.flagKeys; // [new-checkout] result.eventNames; // [purchase] result.toList(); // [{ type: event, line: 5, name: purchase, method: capture }, ...]parse()返回的ParseResult包含五类原始检测数据calls所有 SDK 方法调用、initCallsposthog.init()与构造函数调用、flagAssignmentsflag 结果变量赋值、variantBranches基于 flag 值的 if/switch 分支、functions文件中的函数定义。而events/flagChecks是对calls按方法名过滤后的视图——capture与 Go 的Enqueue归类为事件其余 flag 方法归类为 flag 检查CAPTURE_METHODS集合定义在 parse-result.ts。parseFile 的语言自动识别parseFile()通过path.extname()查表得到语言 ID映射表EXT_TO_LANG_ID定义在 languages.ts扩展名语言 ID.js.mjs.cjsjavascript.jsxjavascriptreact.ts.mts.ctstypescript.tsxtypescriptreact.py.pywpython.gogo.rb.rake.gemspecruby遇到未知扩展名时parseFile()会抛出Unsupported file extension: ...错误见 enricher.ts。三、从 PostHog API 丰富检测结果这是 enricher 最核心的场景parse()只解决代码里用了什么enrichFromApi()解决这些东西在项目里现在是什么状态。const result await enricher.parse(sourceCode, typescript); const enriched await result.enrichFromApi({ apiKey: phx_..., host: https://us.posthog.com, projectId: 12345, }); // Flags with staleness, rollout, experiment info enriched.flags; // [{ flagKey: new-checkout, flagType: boolean, staleness: fully_rolled_out, // rollout: 100, experiment: { name: Checkout v2, ... }, ... }] // Events with definition, volume, unique users enriched.events; // [{ eventName: purchase, verified: true, lastSeenAt: 2025-04-01, // tags: [revenue], stats: { volume: 12500, uniqueUsers: 3200 }, ... }] // Flat list combining both enriched.toList(); // [{ type: event, name: purchase, verified: true, volume: 12500, ... }, // { type: flag, name: new-checkout, flagType: boolean, staleness: fully_rolled_out, ... }] // Source code with inline annotation comments enriched.toComments(); // // [PostHog] Event: purchase (verified) — 12,500 events — 3,200 users // posthog.capture(purchase, { amount: 99 }); // // // [PostHog] Flag: new-checkout — boolean — 100% rolled out — STALE (fully_rolled_out) // const flag posthog.getFeatureFlag(new-checkout);enrichFromApi()内部会并发发起最多五类 API 请求见 parse-result.tsgetFeatureFlags()拉取项目下最多 500 个未删除的 feature flag/feature_flags/?limit500getExperiments()拉取实验列表用于把 flag 关联到实验getEventDefinitions(eventNames)按检测到的事件名拉取事件定义getEventStats(eventNames)按检测到的事件名拉取调用量与独立用户数getFlagEvaluationStats(flagKeys, 7)近 7 天 flag 的评估次数与评估用户数。每个请求默认带 10 秒超时timeoutMsHTTP 层封装在 posthog-api.ts 中请求头使用Authorization: Bearer apiKey。值得注意的是所有请求通过Promise.allSettled并行执行——某个接口失败不会中断整体流程失败项会降级为[]/ 空Map并通过日志警告getFlagEvaluationStats失败还会在结果中标记evaluationStatsError: true。这保证了丰富过程对上游 API 的抖动有较强的容错性。另外enrichFromApi只请求检测到且为非动态的事件名与 flag keyeventNames会过滤掉dynamic事件见 parse-result.ts因为动态事件名无法在 API 侧精确匹配。通过 publicHost 分离 API 与公开链接EnricherApiConfig支持设置publicHostinterface EnricherApiConfig { apiKey: string; host: string; // e.g. https://us.posthog.com publicHost?: string; projectId: number; }当 API 请求走私有代理、而注释中生成的 flag 链接需要指向公开的 PostHog URL 时设置publicHost即可。生成链接的逻辑在 parse-result.ts取publicHost ?? host并去除末尾斜杠拼出${host}/project/${projectId}/feature_flags/${flag.id}。除上述字段外types.ts 还定义了可选的timeoutMs单次 API 请求超时默认 10000 毫秒。四、支持的语言与可检测方法官方支持 7 种语言 ID完整能力矩阵如下LanguageIDCaptureFlagsInitVariantsJavaScriptjavascriptyesyesyesyesTypeScripttypescriptyesyesyesyesJSXjavascriptreactyesyesyesyesTSXtypescriptreactyesyesyesyesPythonpythonyesyesyesyesGogoyesyesyesyesRubyrubyyesyesyesyes从 languages.ts 的方法集合定义看各语言检测的方法存在明显差异这是由各官方 SDK 的 API 形态决定的JS/TS含 JSX/TSX捕获captureflag 方法包括getFeatureFlag、isFeatureEnabled、getFeatureFlagPayload、getFeatureFlagResult、isFeatureFlagEnabled、getRemoteConfig。Python捕获captureflag 方法为蛇形命名——feature_enabled、is_feature_enabled、get_feature_flag、get_feature_flag_payload、get_remote_config。Go捕获方法特殊为Enqueue对应posthog-go的Enqueue(posthog.Capture{...})风格flag 方法为驼峰的GetFeatureFlag、IsFeatureEnabled、GetFeatureFlagPayload。Ruby捕获captureflag 方法为is_feature_enabled、get_feature_flag、get_feature_flag_payload、get_remote_config_payload。此外还有两点实现细节值得注意Python/Ruby 的 capture 特殊处理见 call-detector.tsPython 的capture(distinct_id, event, ...)第一个位置参数是distinct_id而非事件名Ruby 的事件名在event:关键字参数中因此通用查询会跳过这两种语言的 capture改由专用的pythonCaptureCalls/rubyCaptureCalls查询处理支持关键字参数、哈希键值对等形态。Go 的结构体风格调用client.Enqueue(posthog.Capture{Event: purchase})、client.GetFeatureFlag(posthog.FeatureFlagPayload{Key: my-flag})这类调用由goStructCalls查询单独捕获languages.ts。客户端别名的解析默认识别名为posthog、client、ph的客户端变量CLIENT_NAMESlanguages.ts。但真实项目里 SDK 实例常被重命名如const ph posthog.init(...)。检测器会通过clientAliases/constructorAliases/destructuredMethods三组 tree-sitter 查询解析本地别名findAliases见 alias-resolver.ts例如const analytics posthog.init(phc_xxx, { host: https://us.posthog.com }); analytics.capture(purchase); // 通过别名识别为 PostHog 调用 const { capture, getFeatureFlag } posthog; // 解构出来的方法也能识别五、检测原理tree-sitter 查询与包装函数enricher 不运行代码而是把源码交给web-tree-sitter解析为 AST再用每语言定制的 S-expression 查询在 AST 上匹配调用模式。整套查询定义集中在 languages.ts典型模式包括postHogCalls匹配client.method(key)形态的成员调用覆盖字符串字面量与模板字符串两种 key 写法JS/TSidentifierArgCalls匹配首参为标识符非字面量的调用用于标记dynamic事件dynamicCalls兜底匹配首参为任意表达式的调用flagAssignments匹配const flag posthog.getFeatureFlag(key)含await形式与 Python/Go/Ruby 的赋值语句functions匹配函数声明、导出函数、箭头函数、方法定义为后续包装函数分析提供函数边界。包装函数wrapper检测真实项目里经常封装一层自己的上报函数例如export function track(name) { posthog.capture(name); }。enricher 支持识别这类内部调用 SDK 方法的用户函数并将track(checkout_started)这类调用合成为底层 SDK 调用结果中通过viaWrapper: track标注来源WrapperClassification分为fixed-key与pass-through两种见 types.ts。跨文件的包装函数解析由 import-resolver.ts 与ParseContextwrappersByLocalName/namespaceWrapperstypes.ts协同完成——解析器定位import边ImportEdge记录本地名、导入名、是否默认/命名空间导入以及解析后的绝对路径调用方再把另一文件的 wrapper 知识注入检测过程避免检测器自身成为 I/O 层。在PostHogEnricher层面enricher.ts按文件解析 wrapper 的结果会按绝对路径 mtime缓存最多 1024 条LRU 淘汰源码超过 1 MB 或内容不含posthog/PostHog字样的文件直接跳过解析——这是为桌面端批量扫描大仓库做的性能与正确性权衡。六、API 参考PostHogEnricher主入口类持有 tree-sitter parser 的生命周期const enricher new PostHogEnricher(); const result await enricher.parse(source, languageId); const result await enricher.parseFile(/path/to/file.ts); enricher.dispose();方法说明constructor()创建 enricher。各语言的 WASM grammar 已随包发布并在运行时自动定位无需手动配置parse(source, languageId)用显式语言 ID 解析源码字符串parseFile(filePath)读取文件并解析根据扩展名自动识别语言isSupported(langId)判断某语言 ID 是否受支持supportedLanguages受支持语言 ID 列表updateConfig(config)定制检测行为见下文DetectionConfig并清空 wrapper 缓存dispose()释放 parser 等资源ParseResultenricher.parse()的返回值包含检测到的全部 PostHog SDK 用法属性 / 方法类型说明callsPostHogCall[]所有检测到的 SDK 方法调用initCallsPostHogInitCall[]posthog.init()与构造函数调用含 token、apiHost、配置属性flagAssignmentsFlagAssignment[]flag 结果变量赋值含变量名、方法、flag key、行号、是否有类型标注variantBranchesVariantBranch[]基于 flag 值的 if/switch 分支含条件行、起止行、variant keyfunctionsFunctionInfo[]文件中的函数定义含参数、是否组件、函数体起止行与缩进eventsCapturedEvent[]仅 capture 调用flagChecksFlagCheck[]仅 flag 方法调用flagKeysstring[]去重后的 flag key 列表eventNamesstring[]去重后的非动态事件名列表toList()ListItem[]按行号排序的扁平化 SDK 用法列表含init/event/flag三种类型enrichFromApi(config)PromiseEnrichedResult调用 PostHog API 丰富结果EnrichedResultenrichFromApi()或enrich()的返回值将检测结果与 PostHog 项目上下文合并属性 / 方法类型说明flagsEnrichedFlag[]按 key 分组的 flag含类型、陈旧度、发布比例、实验信息eventsEnrichedEvent[]按名称分组的事件含定义、统计、标签toList()EnrichedListItem[]带全部元数据的扁平列表toComments()string带行内注释标注的源码toInlineComments(options)string行内注释变体可通过includeEventDescriptions/includeExperimentNames控制是否包含事件描述与实验名默认均为trueEnrichedFlag与EnrichedEvent的完整结构interface EnrichedFlag { flagKey: string; flagType: boolean | multivariate | remote_config; staleness: StalenessReason | null; rollout: number | null; variants: { key: string; rollout_percentage: number }[]; flag: FeatureFlag | undefined; experiment: Experiment | undefined; occurrences: FlagCheck[]; } interface EnrichedEvent { eventName: string; verified: boolean; lastSeenAt: string | null; tags: string[]; stats: { volume?: number; uniqueUsers?: number } | undefined; definition: EventDefinition | undefined; occurrences: CapturedEvent[]; }两个 getter 内部都按 key/name 聚合Map 累积occurrences并用缓存避免重复计算见 enriched-result.ts。DetectionConfig检测行为定制updateConfig()接受DetectionConfigtypes.tsinterface DetectionConfig { additionalClientNames: string[]; // 额外的客户端变量名默认 [] additionalFlagFunctions: string[]; // 额外的 flag 方法名默认 [] detectNestedClients: boolean; // 是否检测嵌套客户端成员表达式默认 true onError?: (message: string, error?: unknown) void; }例如你的代码里把 SDK 实例命名为analytics且自定义了 flag 函数可配置enricher.updateConfig({ additionalClientNames: [analytics], additionalFlagFunctions: [isExperimentOn], });七、底层检测 APIPostHogDetectorParseResult之上的高层 API 由五个底层检测器聚合而成parse()内部用Promise.allSettled并发执行任一检测失败仅告警不阻塞见 enricher.ts。这套低层 API 同样对外导出PostHog 官方 VSCode 扩展即使用同一套接口import { PostHogDetector } from posthog/enricher; const detector new PostHogDetector(); const calls await detector.findPostHogCalls(source, typescript); const initCalls await detector.findInitCalls(source, typescript); const branches await detector.findVariantBranches(source, typescript); const assignments await detector.findFlagAssignments(source, typescript); const functions await detector.findFunctions(source, typescript); detector.dispose();此外还包含findWrappers同文件 wrapper 定义与findImports跨文件 import 边解析。从包导出面index.ts看enricher 还导出enrichSource共享丰富流水线供posthog/shared之外的调用方直接复用、PostHogApi、setLogger、EXT_TO_LANG_ID以及toSerializabletRPC/IPC 边界的序列化。Flag 分类工具函数import { classifyFlagType, classifyStaleness } from posthog/enricher; classifyFlagType(flag); // boolean | multivariate | remote_config classifyStaleness(key, flag, experiments, opts); // StalenessReason | nullclassifyFlagTypeflag-classification.ts的判定逻辑filters.multivariate下存在非空variants→multivariatefilters.payloads中存在非空值 →remote_config否则 →boolean。classifyStalenessstale-flags.ts按优先级返回以下陈旧原因之一不陈旧则返回null原因触发条件not_in_posthog该 flag key 在 PostHog 项目中不存在inactiveflag 存在但处于非活跃active: false状态experiment_complete该 flag 关联的实验已结束存在end_datefully_rolled_outflag 100% 发布且无属性条件、无多变量且创建时间超过staleFlagAgeDays天默认 30 天可通过opts.staleFlagAgeDays调整完全发布的判定由isFullyRolledOut实现要求所有filters.groups的rollout_percentage 100且无属性条件且 flag 不包含多变量变体flag-classification.ts。配套的extractRollout/extractVariants/extractConditionCount分别提取发布比例、多变量变体列表与带属性条件的发布组数量。八、日志与错误可见性默认情况下 enricher 的告警是静默的检测失败、API 失败都被吞掉。需要排查时启用日志import { setLogger } from posthog/enricher; setLogger({ warn: console.warn });启用后解析阶段与丰富阶段的所有降级都会打印例如enricher: calls detection failed、enricher: getFeatureFlags failed等见 enricher.ts 与 parse-result.ts。九、安装、构建与 Grammar 管理包内grammars/目录已随发布包含 6 份语言 WASM 与tree-sitter.wasm运行时package.json 的files字段显式打包了dist/**/*与grammars/**/*运行时由ParserManager自动定位——普通使用者无需任何手动 setup。依赖层面运行时仅需web-tree-sitter与 workspace 内的posthog/shared构建使用tsuppnpm build测试使用vitestpnpm test。开发者在本地修改/重建 grammar 时运行pnpm fetch-grammars该脚本scripts/fetch-grammars.cjs会检查tree-sitter-cli是否可用npx tree-sitter --version缺失则提示先npm install -g tree-sitter-cli从web-tree-sitter拷贝核心运行时tree-sitter.wasm逐个安装 grammar 包并用tree-sitter build --wasm编译产物写入grammars/。其中各 grammar 版本被固定以匹配web-tree-sitter0.24.x的 ABI v14JavaScript 固定0.23.1、Python 固定0.23.5、Go 固定0.23.4、Ruby 固定0.23.1TypeScript 使用其仓库内的typescript/与tsx/子目录分别产出两份 WASM。若机器缺少 emscripten 导致 WASM 构建失败脚本会提示使用tree-sitter build --wasm --docker或手工放置预编译.wasm文件。十、在 PostHog 桌面端中的实际应用enricher 不是孤立的工具包而是 PostHog 桌面应用代码内上下文能力的基石。仓库中 products/desktop/packages/agent/src/enrichment/file-enricher.ts 直接依赖posthog/enricher用于在桌面端对用户打开/扫描的源码文件执行同样的检测与丰富流水线。结合enrichSourceenrich-source.ts的共享流水线与toSerializableserialize.ts的 tRPC/IPC 序列化边界检测结果可以在桌面端主进程与渲染进程之间稳定传递。参考实现中toComments()生成的注释版本兼顾了 JSX 场景——当调用点位于 JSX 元素内部时inJsx标记//行注释不再合法注释格式化会退化为合适的呈现方式见 comment-formatter.ts 及其测试 comment-formatter.test.ts。小结posthog/enricher用一套紧凑的 API 完成了源码内 SDK 用法清单 → 与 PostHog 项目元数据关联 → 生成自解释源码的完整闭环。把握三个要点即可快速上手检测与丰富是两层parse()是纯静态分析零网络enrichFromApi()才发起 API 请求前者可以在 CI/本地任意复用后者需要合法的项目 API Key 与 projectId。语言差异内置处理Go 的Enqueue、Python 的蛇形命名、Ruby 的关键字参数 capture 都由语言家族配置自动适配上层无需关心。失败默认降级所有检测/API 步骤均为allSettled语义配合setLogger可在需要时观察每一步的真实状态。如需深入源码推荐按此顺序阅读languages.ts查询与方法集→ call-detector.ts调用捕获→ enricher.ts生命周期与缓存→ enriched-result.ts聚合与注释生成配套测试见 detector.test.ts、enricher.test.ts 与 wrapper-detector.test.ts。【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考