AST(Abstract Syntax Tree,抽象语法树),简单说就是:把源代码变成一棵“结构树”
它不关心空格、换行、注释这些表面格式,只保留语法结构:哪里是函数、哪里是变量、谁调用了谁、参数是什么、返回什么。


AST 有什么特点?

  • 不是普通文本:是结构化的树。
  • 由解析器生成:比如 Babel、TypeScript、ESLint、Vite、Rollup 都会生成 AST。
  • 可以反向变回代码:改完 AST 后,通常还能打印回源码。
  • 适合程序处理:比直接处理字符串更准确、更安全。

AI 能拿 AST 做什么?

AI 可以直接或间接利用 AST 来做很多事:

  1. 理解代码结构

    • 找出所有函数、类、接口、导出、依赖。
    • 前文里的“AST 分析 apps/web”就是在做这件事:列出文件、入口、导出、测试、构建配置等。
  2. 代码搜索和导航

    • 按结构搜索:“找所有调用 connectWorkerHost 的地方”。
    • 比字符串搜索更准,不会误匹配注释或变量名。
  3. 代码生成和修改

    • AI 不直接乱改文本,而是生成或修改 AST,再打印成代码。
    • 这样语法更不容易出错,比如自动加 import、改变量名、插入函数。
  4. 自动重构

    • 提取函数、重命名、迁移 API、拆分模块。
    • 比如把所有 React.createElement 改成 JSX。
  5. 静态分析和查错

    • 发现未使用变量、循环依赖、类型不匹配、潜在漏洞。
    • 做数据流分析、污点分析,比如“用户输入有没有直接进 SQL”。
  6. 代码摘要和文档

    • 遍历 AST 提取函数签名、参数、返回值,自动生成文档。
  7. 测试生成

    • 根据条件分支、函数入口,AI 可以生成更针对性的测试用例。
  8. 跨语言转换

    • 把一种语言的 AST 映射到另一种语言的结构,辅助翻译代码。
  9. 减少 AI 幻觉

    • 让 AI 基于 AST 操作,而不是凭空改文本。
    • 比如 AI 输出“把某个节点替换掉”,由工具执行,结果更可靠。

一句话总结

AST 是代码的“骨架图”。
人看代码是看文字,AI 和工具看代码时,更希望看 AST。
AI 用 AST 可以更准确地理解、搜索、修改、生成、分析和重构代码

插件效果:(解析harnes web客户端为例)

D:\harness\3\deepseek-harness\apps\web。请用ast解析这个文件夹下的代码。

我现在已经彻底理解了代码库。以下是 apps/web 的完整 AST 分析:


D:\harness\3\deepseek-harness\apps\web 的 AST 分析

此文件夹是 DeepSeek Harness 的 Web 客户端构建产物。它包含 176 个文件,分为 4 个类别:

类别数量描述
src/4浏览器入口点 + 存根
vite.config.ts1Vite 构建配置
tests/~90端到端 + 快照测试
lib/types/~20生成的类型声明
dist/~55构建输出产物
stress-tests/1性能压力测试

1. 源码(src/)— 4 个文件

src/main.ts(6 行)

  • 导入:从 @deepseek-ai/dsh-client-web 导入 AppWebEntry
  • 入口点:获取 #root 元素,创建 new AppWebEntry(el).run()

src/preview.ts(14 行)

  • Worker 预览引导:从 @deepseek-ai/dsh-experimental-webworker-runtime 导入 DshWorker(Web Worker 运行时)、chooseWorkerHostSourceconnectWorkerHostIMAGE_FILE_NAME
  • 顶层 await:调用 chooseWorkerHostSource({ image }),然后调用 connectWorkerHost(new DshWorker(...), { image, overlays })

src/node-module-stub.ts(12 行)

  • 导出
    名称类型签名
    createRequire函数(): never — 抛出错误
    LoadHookContext类型never
  • node:module 的浏览器替代存根

src/vite-env.d.ts — 单行 /// <reference types="vite/client" />


2. 构建配置(vite.config.ts)— 235 行

导入fs/promisesurlvite(defineConfig、Plugin)、@vitejs/plugin-react、自定义 clientBuildEnvironmentDefines

工具函数

函数参数用途
src(rel)string从配置目录解析相对路径
escapeHtmlText(value)string为 HTML 标题转义 &<>
npmPackageOf(id)string从解析的模块 id 中提取 npm 包名

Vite 插件(3 个自定义插件):

插件钩子用途
clientDocumentTitle()transformIndexHtmlDSH_CLIENT_TITLE 环境变量注入 <title>
rejectStandaloneServe()config对裸 vite dev/vite preview 抛出错误
emitPreviewPage()generateBundlecloseBundle拼接带 Worker 引导脚本标签的 preview.html

Vite 配置base: './'target: 'es2022'、react 插件、sourcemap

Rollup 输入

  • indexindex.html(主页面)
  • bootstrapsrc/preview.ts(Worker 预览)

分块策略

  • manualChunksVENDOR_PACKAGES(15 个包的 Set:katex、shiki、mdast/micromark 系列)→ vendor 分块
  • @shikijs/langs:启动语法(typescript、shellscript、json)→ vendor;懒加载语法 → 单独的 assets/langs/ 分块
  • 字体 → assets/fonts/
  • entryFileNames:bootstrap 为 preview/,index 为 assets/

解析(Resolve)

  • dedupe: ['react', 'react-dom']
  • aliasnode:modulenode-module-stub.ts

定义(Define)clientBuildEnvironmentDefinesprocess.versions.nodeprocess.execArgvprocess.env.CORDIS_SHARED


3. 测试基础设施 — 关键共享文件

tests/support.ts(199 行)

所有 Web 端到端测试的共享管道。

导出

导出类型用途
DIST_INDEX常量构建后 dist/index.html 的路径
REPO_ROOT常量仓库根目录(向上 4 层)
ZH_BROWSER_LOCALE常量'zh-CN'
newEnglishPage(browser, height)异步函数创建 Playwright 页面(1680×1000、en-US、Asia/Shanghai)
expandTurnProcesses(page)异步函数展开所有 [data-turn-process] 分组
expandOwningTurnProcess(page, target)异步函数展开包含特定定位器的轮次
requireDist()void 函数断言构建后的 dist 存在
probeFreePort()异步函数通过 net.createServer 获取操作系统分配的空闲端口
connectFreshWorkspace(page, root, name)异步函数驱动工作区选择器 → mkdir → 编辑路径 → 打开
connectFreshWorkspaceZh(page, root, name)异步函数中文区域变体
writeComposerDraft(page, input, text)异步函数Lexical 安全的编辑器文本输入(按键,而非填充)
saveFailureShot(page, name)异步函数截图 → .artifacts/
conversationContextKey(kind, id)函数格式化对话上下文键
tests/assembled-boot.ts(305 行)

用于快照测试的基于 Jsdom 的引导 — 无需浏览器即可挂载真实 Web 组合。

接口

  • AssembledPlugin — 扩展 WebBootEntry,带 bundlePath
  • AssembledBootOptionsexclude?: readonly string[]
  • ClientPackageManifestComposedEntryBootCompositionFixtureWindow

关键模块级状态

  • REPO_ROOTBUNDLE_LAYERS(基础 + Web 应用 bundle 补丁)
  • workspacePackageManifests — 所有 packages/*/*/package.jsonMap<string, string>
  • appBoot — 动态导入的 @deepseek-ai/dsh-app-boot 模块
  • PLUGINS — 已加载的有序插件
  • win — 类型转换后的 window as FixtureWindow

函数

函数参数用途
resolvePackageManifest(specifier)string查找工作区包路径
resolveClientExport(packagePath, pkg)string, ClientPackageManifest解析 ./client 导出
comboUrl(ids, rev)string[], string构建 /plugins/??...&rev= URL
loadAssembledPlugins()组合 bundle 补丁 → 有序插件列表
bootGraph(plugins)AssembledPlugin[]构建带 bootstrap/application 批次的 WebBootGraph
bundleTable(graph, plugins)WebBootGraph, AssembledPlugin[]构建所有 bundle/组合的 url→code 映射
installAssembledBootEnv()注册带 vi 存根的 beforeEach/afterEach(ResizeObserver、EventSource、rAF、locale)
mountAssembledApp(search, options)string, AssembledBootOptions通过 __DSH_BOOT__ + eval + AppWebEntry 在 fixture 传输上挂载应用
hasClass(el, name)Element, string按逻辑名称匹配 CSS-module 类
REFRESHING_GOLDEN常量DSH_SNAPSHOT === 'record' || 'refresh'

存根类ResizeObserverStubEventSourceStub

tests/chat-scroll-fixture.ts(250 行)

为滚动/性能测试生成合成的长对话会话 JSONL。

接口ChatScrollFixtureOptionsChatScrollMarkersChatScrollFixture

常量DEFAULT_TURNS = 88TOOL_INTERVAL = 8CODE_INTERVAL = 11

辅助函数

函数用途
text(value)将字符串包装为 {type:'text', text} 数组
suffix(turn)零填充的 3 位轮次编号
markerHelpers(prefix)返回 {user, assistant, tool} 标记工厂
appendSystemPrompt(session, turn, step)追加 system/message
appendRequestHeader(session, turn, step)追加 request/header
appendAssistant(session, turn, step, body)追加带用量的 assistant/message
codeBlock(turn)在 CODE_INTERVAL 时生成 30 行 TypeScript 代码块
appendToolStep(session, markers, turn)追加工具调用 + 2 个 bash 结果
fixtureLog(session)将会话序列化为 JSONL

核心导出createChatScrollFixture(options) — 生成 88 轮对话,包含散文、代码块(%11)、bash 工具调用(%8),所有轮次均已关闭。

tests/scaffold.ts(1597 行 — 最大的文件)

用于浏览器端到端测试的真实 Web 组合引导,使用 Playwright。

导出

导出类型用途
WELCOME_NOTICE_*常量从 ui-settings-models 镜像而来
WebSnapshotMode类型'replay' | 'record' | 'refresh'
WebScaffold接口返回类型:mode、baseUrl、authenticatedUrl、ctx、workspaceCwd、persistenceRoot、harnessHome、hostFetch、whenTurnSettled、close
LaunchOptions接口30+ 个 scaffold 配置选项
webSnapshotMode()函数读取 $DSH_SNAPSHOT
assertFinalWorkspaceSnapshot(...)异步函数将工作区与预期值比较
selectedSessionFixture(path)异步函数解析最高提交的 fixture 代次
recordedSessionFixturePath(path, version)函数计算规范的兄弟路径
launchWebScaffold(options)异步函数主入口 — 引导真实组合
normalizeWebSessionVolatiles(log, ...)函数对 JSONL 中运行本地字符串进行分词
recordFixture(scaffold, sessionId, path)异步函数采集实时会话 → fixture
fixtureUserPrompts(fixtureText)函数从 fixture 中提取用户提示
fixtureIdentity(kind, ordinal)函数从 sha256 生成确定性 UUID
realizeSeedFixture(...)函数替换 {{sessionId}}/{{cwd}} 占位符
parseSeedFixture(fixtureText)函数通过重放读取器解析种子 fixture
renderSeedFixture(headerLine, events)函数将事件渲染为 JSONL
seedSession(scaffold, ..., options)异步函数将 fixture 播种到持久化存储
readPersistedEvents(scaffold, id)异步函数读取存储的会话事件
captureStableAria(page, selector, cwd, ...)异步函数轮询稳定的 aria 快照
captureExpandedTurnProcessAria(...)异步函数展开轮次 + 滚动 + 稳定化 aria
compareOrRefreshGolden(path, actual, mode)异步函数比较或自动刷新 golden
assertFixtureInventory(dir, expected)异步函数验证 fixture 目录内容
watchConsole(page)函数触发式控制台/pageerror 收集器
acknowledgeReloadConnectionLoss(...)函数剥离重载警告

launchWebScaffold() — 主编排器:

  1. 加载 bundle 补丁(基础 + Web 应用 + 可选额外)
  2. 创建临时工作区/持久化目录
  3. 注册 20+ 个 include 补丁(技能隔离、遥测、目录选择器、工具模式、重放 fixture)
  4. 使用 LoaderIncludeGroup 引导 Cordis 上下文
  5. 可选安装 dsh-llm-replay 用于 fixture 重放
  6. 返回带 hostFetchwhenTurnSettledcloseWebScaffold

assertReplaySession() — 将实时会话转录与提交的 fixture 比较,支持刷新模式。


4. 代表性端到端测试文件(样本)

所有端到端测试遵循相同模式:

  • ./scaffold.ts 导入 launchWebScaffold + 辅助函数
  • ./support.ts 导入 newEnglishPageconnectFreshWorkspace
  • beforeAll/afterAll scaffold 生命周期
  • 带 Playwright 浏览器交互的 describe/it
  • 用于快照断言的 captureStableAria + compareOrRefreshGolden
测试文件行数重点
lifecycle-chrome.e2e.ts566工作区首次发送、重载恢复、深色模式主题
smoke-real.e2e.ts869使用真实密钥完整 dsh web 启动,所有屏幕
settings-chrome.e2e.ts713设置模态框、外观/语言/权限行
composer-placeholder.e2e.ts72输入/粘贴空格时的占位符可见性
chat-continuous-conversation.e2e.ts多轮对话流程
navigation-panes.e2e.ts侧边栏/面板导航
scaffold-hermetic.e2e.ts74验证技能发现与宿主根目录的隔离
scaffold-generation.spec.ts76fixture 命名/清单逻辑的单元测试

5. 整体架构图(概念性)

vite.config.ts
  ├── src/main.ts ───────────── AppWebEntry ──► @deepseek-ai/dsh-client-web
  ├── src/preview.ts ────────── Web Worker Runtime(预览引导)
  └── src/node-module-stub.ts ── node:module 的浏览器存根

tests/
  ├── support.ts ─────────── Playwright 辅助函数(页面、端口、工作区)
  ├── scaffold.ts ────────── 引导编排器(launchWebScaffold)
  ├── assembled-boot.ts ──── 用于快照测试的 Jsdom 引导
  ├── chat-scroll-fixture.ts ── 长对话合成 fixture 生成器
  ├── scaffold-hermetic.e2e.ts ── 技能隔离测试
  ├── scaffold-generation.spec.ts ── fixture 命名单元测试
  └── *.e2e.ts ──────────── ~90 个基于浏览器的场景测试

插件实现:

在这里插入图片描述

1. 头部:导入与插件声明

import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
import { parse } from '@babel/parser'
import * as t from '@babel/types'

export const name = 'ast-tool'
export const inject = ['tools']
  • @deepseek-ai/cordis:harness 的插件框架,Context 是所有插件 apply 拿到的上下文。
  • @deepseek-ai/dsh-toolsdefineTool 是定义工具的统一 DSL。
  • @babel/parser / @babel/types:真正的 AST 解析器(Babel),负责词法/语法分析和节点类型判断。
  • inject = ['tools']:声明本插件依赖 tools 服务,框架会在 tools 注册表就绪后才调用 apply(对应 [index.zh.md](file:///d:/harness/3/deepseek-harness/docs/user/develop/basic/index.zh.md) 里的“声明依赖”)。

2. 结果类型定义

  • FuncInfo:一个“函数式”声明(函数/箭头变量/类方法),含 kindnameparamsline
  • DeclInfo:一个顶层声明(import/function/variable/class/export),含 kindnameline

3. 三个辅助函数(纯函数,无副作用)

paramNames [L17-L24](file:///d:/harness/3/deepseek-harness/scratch-plugin/src/ast-tool.ts#L17-L24):从函数节点提取参数名,处理三种情况——普通标识符、带默认值(b = 1)、剩余参数(...rest),其它模式统一记为 (pattern)

collectFunctions [L26-L49](file:///d:/harness/3/deepseek-harness/scratch-plugin/src/ast-tool.ts#L26-L49):递归遍历顶层语句,收集函数式声明:

  • 函数声明 function foo(){}
  • 变量声明中初始化为箭头/函数表达式的(const bar = (x) => ...
  • export 包裹的声明(递归解包)
  • 类声明里的方法

collectDecls [L58-L88](file:///d:/harness/3/deepseek-harness/scratch-plugin/src/ast-tool.ts#L58-L88):收集所有顶层命名声明,包括 import 的本地名、变量、类、命名导出、默认导出等。

4. apply 与工具定义

[apply](file:///d:/harness/3/deepseek-harness/scratch-plugin/src/ast-tool.ts#L90-L203) 里通过 ctx.tools.register(defineTool({...})) 注册工具。defineTool 的几个关键字段:

字段作用
name: 'ast_parse'模型看到的工具名
description发给模型的说明,告诉它工具的用途、参数含义、只读不执行
parameters输入参数 schema(code 必填、language 可选枚举 js/jsx/ts/tsx)
output.schema返回值规范结构(ok/language/syntaxErrors/declarations/functions
output.render把规范值转成面向模型的文本(这里直接 JSON.stringify
execute真正的解析逻辑

execute 的核心流程 [L162-L201](file:///d:/harness/3/deepseek-harness/scratch-plugin/src/ast-tool.ts#L162-L201):

  1. 根据 language 选择 Babel 插件:ts/tsx 启用 typescriptjsx/tsx 启用 jsx
  2. errorRecovery: true 解析——即使有语法错误也不抛异常,而是把错误放进 ast.errors
  3. 成功时返回 ok: true + 语法错误列表 + 声明列表 + 函数列表。
  4. 解析器真的抛出异常时(极少见),返回 ok: false 并带上错误信息,保证工具不崩溃。

5. 使用方式

运行(已在 [cordis.yml](file:///d:/harness/3/deepseek-harness/scratch-plugin/cordis.yml#L7-L9) 注册):

pnpm dsh web --patch ./scratch-plugin/cordis.yml

打开 http://127.0.0.1:3080,直接对模型说类似:

用 ast_parse 解析下面这段 TypeScript 代码,告诉我有哪些函数和参数:function add(a, b) { return a + b }

模型会调用 ast_parsecode 传源码、language"ts",返回类似:

{
  "ok": true,
  "language": "ts",
  "syntaxErrors": [],
  "declarations": [
    { "kind": "function", "name": "add", "line": 1 }
  ],
  "functions": [
    { "kind": "function", "name": "add", "params": ["a", "b"], "line": 1 }
  ]
}

6. 两个设计要点

  • 只读、不执行:description 里明确写了 read-only analysis tool,解析出的 AST 不会运行源码,符合“AST 解析”而非“代码执行”的语义。
  • 优雅降级:语法错误不会被当作工具异常抛出,而是通过 ok: false + syntaxErrors 返回,模型能据此继续诊断,而不是直接中断。

如果你希望它进一步返回完整 AST 节点树(而非仅摘要)、或增加跨函数调用关系分析,可以告诉我,我在现有基础上扩展 output.schemacollect* 函数。

代码:
ast-tool.ts

import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
import { parse } from '@babel/parser'
import * as t from '@babel/types'

export const name = 'ast-tool'
export const inject = ['tools']

/** Collect named function-like declarations recursively (functions, methods, arrow vars). */
interface FuncInfo {
  kind: string
  name: string
  params: string[]
  line: number
}

function paramNames(node: t.FunctionParent): string[] {
  return node.params.map((p) => {
    if (t.isIdentifier(p)) return p.name
    if (t.isAssignmentPattern(p)) return t.isIdentifier(p.left) ? p.left.name : '(pattern)'
    if (t.isRestElement(p)) return `...${t.isIdentifier(p.argument) ? p.argument.name : '(pattern)'}`
    return '(pattern)'
  })
}

function collectFunctions(program: t.Node, out: FuncInfo[] = []): FuncInfo[] {
  for (const node of program as unknown as t.Node[]) {
    if (t.isFunctionDeclaration(node) && node.id) {
      out.push({ kind: 'function', name: node.id.name, params: paramNames(node), line: node.loc?.start.line ?? 0 })
    } else if (t.isVariableDeclaration(node)) {
      for (const decl of node.declarations) {
        if (t.isIdentifier(decl.id) && (t.isArrowFunctionExpression(decl.init) || t.isFunctionExpression(decl.init))) {
          out.push({ kind: 'function', name: decl.id.name, params: paramNames(decl.init), line: decl.loc?.start.line ?? 0 })
        }
      }
    } else if (t.isExportNamedDeclaration(node) && node.declaration) {
      collectFunctions([node.declaration] as unknown as t.Node[], out)
    } else if (t.isExportDefaultDeclaration(node) && node.declaration) {
      collectFunctions([node.declaration] as unknown as t.Node[], out)
    } else if (t.isClassDeclaration(node)) {
      for (const member of node.body.body) {
        if (t.isClassMethod(member) && member.key.type === 'Identifier') {
          out.push({ kind: 'class method', name: member.key.name, params: paramNames(member), line: member.loc?.start.line ?? 0 })
        }
      }
    }
  }
  return out
}

/** Collect top-level declarations with names. */
interface DeclInfo {
  kind: string
  name: string
  line: number
}

function collectDecls(program: t.Node, out: DeclInfo[] = []): DeclInfo[] {
  for (const node of program as unknown as t.Node[]) {
    if (t.isFunctionDeclaration(node) && node.id) {
      out.push({ kind: 'function', name: node.id.name, line: node.loc?.start.line ?? 0 })
    } else if (t.isVariableDeclaration(node)) {
      for (const decl of node.declarations) {
        if (t.isIdentifier(decl.id)) {
          out.push({ kind: 'variable', name: decl.id.name, line: decl.loc?.start.line ?? 0 })
        }
      }
    } else if (t.isClassDeclaration(node) && node.id) {
      out.push({ kind: 'class', name: node.id.name, line: node.loc?.start.line ?? 0 })
    } else if (t.isImportDeclaration(node)) {
      for (const spec of node.specifiers) {
        const local = t.isImportSpecifier(spec) || t.isImportDefaultSpecifier(spec) ? spec.local.name : spec.local.name
        out.push({ kind: 'import', name: local, line: node.loc?.start.line ?? 0 })
      }
    } else if (t.isExportNamedDeclaration(node)) {
      if (node.declaration) {
        collectDecls([node.declaration] as unknown as t.Node[], out)
      } else {
        for (const spec of node.specifiers) {
          out.push({ kind: 'export', name: t.isExportSpecifier(spec) ? spec.local.name : '(namespace)', line: node.loc?.start.line ?? 0 })
        }
      }
    } else if (t.isExportDefaultDeclaration(node)) {
      out.push({ kind: 'export default', name: '(default)', line: node.loc?.start.line ?? 0 })
    }
  }
  return out
}

export function apply(ctx: Context): void {
  ctx.tools.register(defineTool({
    name: 'ast_parse',
    description:
      'Parse JavaScript/TypeScript source code into a structured syntax summary using an AST parser. '
      + 'Returns syntax errors (if any), top-level declarations (functions, variables, classes, imports, exports) '
      + 'and every named function/method with its parameters and line numbers. This is a read-only analysis tool: '
      + 'it never executes the code. Pass `code` as a plain string and `language` to select the dialect: '
      + '"js" (JavaScript), "jsx" (JavaScript + JSX), "ts" (TypeScript), or "tsx" (TypeScript + JSX); default is "js".',
    parameters: {
      code: { type: 'string', required: true, description: 'The JavaScript/TypeScript source code to parse.' },
      language: {
        type: 'string',
        enum: ['js', 'jsx', 'ts', 'tsx'],
        description: 'Source dialect. Defaults to "js".',
      },
    },
    output: {
      schema: {
        type: 'object',
        additionalProperties: false,
        properties: {
          ok: { type: 'boolean', required: true },
          language: { type: 'string', required: true },
          syntaxErrors: {
            type: 'array',
            required: true,
            items: {
              type: 'object',
              additionalProperties: false,
              properties: {
                message: { type: 'string', required: true },
                line: { type: 'number', required: true },
                column: { type: 'number', required: true },
              },
            },
          },
          declarations: {
            type: 'array',
            required: true,
            items: {
              type: 'object',
              additionalProperties: false,
              properties: {
                kind: { type: 'string', required: true },
                name: { type: 'string', required: true },
                line: { type: 'number', required: true },
              },
            },
          },
          functions: {
            type: 'array',
            required: true,
            items: {
              type: 'object',
              additionalProperties: false,
              properties: {
                kind: { type: 'string', required: true },
                name: { type: 'string', required: true },
                params: {
                  type: 'array',
                  required: true,
                  items: { type: 'string' },
                },
                line: { type: 'number', required: true },
              },
            },
          },
        },
      },
      render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
    },
    async execute(args) {
      const language = args.language ?? 'js'
      try {
        const ast = parse(args.code, {
          sourceType: 'module',
          plugins: [
            language === 'ts' || language === 'tsx' ? 'typescript' : null,
            language === 'jsx' || language === 'tsx' ? 'jsx' : null,
          ].filter((p): p is NonNullable<typeof p> => p !== null),
          errorRecovery: true,
          ranges: false,
          attachComment: false,
        })
        const body = ast.program.body
        return {
          ok: true,
          language,
          syntaxErrors: (ast.errors ?? []).map((err) => ({
            message: err.message,
            line: err.loc?.line ?? 0,
            column: err.loc?.column ?? 0,
          })),
          declarations: collectDecls(body as unknown as t.Node),
          functions: collectFunctions(body as unknown as t.Node),
        }
      } catch (error) {
        const err = error as { message?: string; loc?: { line?: number; column?: number } }
        return {
          ok: false,
          language,
          syntaxErrors: [{
            message: err.message ?? String(error),
            line: err.loc?.line ?? 0,
            column: err.loc?.column ?? 0,
          }],
          declarations: [],
          functions: [],
        }
      }
    },
  }))
}

cordis.yml

- insert:
    - id: ast
      name: 'D:deepseek-harness/scratch-plugin/src/ast-tool.ts'

package.json

{
  "name": "scratch-plugin-ast",
  "private": true,
  "type": "module",
  "dependencies": {
    "@babel/parser": "^7.29.7",
    "@babel/types": "^7.29.0"
  }
}
Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐