Files

65 lines
3.1 KiB
JavaScript

import test from 'node:test'
import assert from 'node:assert/strict'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import * as plugin from '../index.js'
import { findAstGrep } from '../lib/sg.js'
const DSH = '/usr/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai'
async function harness() {
const { default: LocalFileSystem } = await import(`${DSH}/dsh-fs-local/lib/index.js`)
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'omp-ast-'))
const fsImpl = Object.create(LocalFileSystem.prototype)
Object.assign(fsImpl, { config: { cwd: dir, diffBasisMaxBytes: 1 << 24 }, internals: {}, locks: new Map() })
const tools = new Map()
const ctx = { fs: fsImpl, tools: { register(d) { tools.set(d.name, d); return () => {} } }, emit() {}, effect() {}, get() {} }
plugin.apply(ctx, {})
const exec = { agent: { session: { header: { cwd: dir } } }, signal: new AbortController().signal }
const call = async (name, args) => { const t = tools.get(name); const v = await t.execute(args, exec); return v }
return { dir, call, w: (f, s) => fs.writeFileSync(path.join(dir, f), s), r: (f) => fs.readFileSync(path.join(dir, f), 'utf8') }
}
const skip = !findAstGrep()
test('ast_grep finds calls with captures', { skip }, async () => {
const h = await harness()
h.w('a.js', 'function f(x) {\n console.log(x)\n console.log(x, 2)\n log(x)\n}\n')
const v = await h.call('ast_grep', { pat: 'console.log($$$ARGS)' })
assert.match(v.text, /^2 matches in 1 file/)
assert.match(v.text, /^2: console\.log\(x\)$/m)
assert.match(v.text, /\$\$\$ARGS=\[x, 2\]/)
})
test('ast_edit previews, then applies by id; multiple ops chain', { skip }, async () => {
const h = await harness()
h.w('a.py', 'def f(a):\n print(a)\n print(a, 1)\n')
const p = await h.call('ast_edit', { ops: [{ pat: 'print($$$A)', out: 'log($$$A)' }, { pat: 'log($X, 1)', out: 'log($X)' }], paths: ['a.py'] })
assert.match(p.text, /Staged as proposal (\w+)/)
assert.equal(h.r('a.py'), 'def f(a):\n print(a)\n print(a, 1)\n') // untouched
const id = p.proposal
const a = await h.call('ast_edit', { apply: id })
assert.match(a.text, /Applied proposal/)
assert.equal(h.r('a.py'), 'def f(a):\n log(a)\n log(a)\n')
await assert.rejects(h.call('ast_edit', { apply: id }), /No pending proposal/)
})
test('ast_edit refuses stale proposal', { skip }, async () => {
const h = await harness()
h.w('b.ts', 'const a = foo(1)\n')
const p = await h.call('ast_edit', { ops: [{ pat: 'foo($A)', out: 'bar($A)' }], paths: ['b.ts'] })
h.w('b.ts', 'const a = foo(2)\n')
await assert.rejects(h.call('ast_edit', { apply: p.proposal }), /changed since the preview/)
})
test('ast_edit reject and no-match', { skip }, async () => {
const h = await harness()
h.w('c.go', 'package main\nfunc main() { println("x") }\n')
const n = await h.call('ast_edit', { ops: [{ pat: 'nothing($A)', out: 'x' }], paths: ['.'] })
assert.match(n.text, /No changes/)
const p = await h.call('ast_edit', { ops: [{ pat: 'println($A)', out: 'fmt.Println($A)' }], paths: ['c.go'] })
const r = await h.call('ast_edit', { reject: p.proposal })
assert.match(r.text, /discarded/)
})