Initial commit: omp-ast DSH plugin (oh-my-pi port) with docs and tests
This commit is contained in:
commit
408d0b3066
9 files changed
+488
No files matched your search
@@ -0,0 +1,60 @@
|
||||
// ast-grep CLI wrapper.
|
||||
import { execFile } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
let sgPath
|
||||
export function findAstGrep(extra = []) {
|
||||
if (sgPath !== undefined) return sgPath
|
||||
const dirs = [...extra, ...(process.env.PATH || '').split(path.delimiter), path.join(process.env.HOME || '', '.local/bin'), '/usr/local/bin']
|
||||
sgPath = null
|
||||
for (const d of dirs) {
|
||||
if (!d) continue
|
||||
const p = path.join(d, 'ast-grep') // `sg` collides with util-linux's setgroups on many distros
|
||||
try { fs.accessSync(p, fs.constants.X_OK); sgPath = p; break } catch {}
|
||||
}
|
||||
return sgPath
|
||||
}
|
||||
|
||||
export function run(bin, args, { cwd, signal, timeoutMs = 120_000, maxBuffer = 64 * 1024 * 1024 } = {}) {
|
||||
return new Promise((resolve) => {
|
||||
execFile(bin, args, { cwd, signal, timeout: timeoutMs, maxBuffer, encoding: 'utf8' }, (error, stdout, stderr) => {
|
||||
resolve({ code: error ? (typeof error.code === 'number' ? error.code : 1) : 0, stdout: stdout ?? '', stderr: stderr ?? '', error })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a pattern (optionally with rewrite) and return parsed matches.
|
||||
* @returns {{matches: object[], warnings: string[]}}
|
||||
*/
|
||||
export async function search({ bin, pattern, rewrite, lang, paths, cwd, globs, strictness, selector, signal }) {
|
||||
const args = ['run', '--pattern', pattern, '--json=compact', '--color=never']
|
||||
if (rewrite !== undefined) args.push('--rewrite', rewrite)
|
||||
if (lang) args.push('--lang', lang)
|
||||
if (strictness) args.push('--strictness', strictness)
|
||||
if (selector) args.push('--selector', selector)
|
||||
for (const g of globs ?? []) args.push('--globs', g)
|
||||
args.push('--', ...(paths?.length ? paths : ['.']))
|
||||
const r = await run(bin, args, { cwd, signal })
|
||||
const warnings = r.stderr.split('\n').map((l) => l.trim()).filter((l) => l && !/^See also|^Help: .*playground/i.test(l))
|
||||
if (r.code !== 0 && !r.stdout.trim().startsWith('[')) {
|
||||
throw new Error(`ast-grep failed: ${warnings.join(' ') || r.error?.message || `exit ${r.code}`}`)
|
||||
}
|
||||
let matches = []
|
||||
try { matches = JSON.parse(r.stdout || '[]') } catch (e) { throw new Error(`ast-grep returned malformed JSON: ${e.message}`) }
|
||||
return { matches, warnings }
|
||||
}
|
||||
|
||||
/** Apply replacements (byte offsets) to file contents; returns new text. */
|
||||
export function applyReplacements(buf, reps) {
|
||||
const sorted = [...reps].sort((a, b) => b.start - a.start)
|
||||
let out = buf
|
||||
let lastStart = Infinity
|
||||
for (const r of sorted) {
|
||||
if (r.end > lastStart) continue // overlapping: keep the outermost-first earlier one
|
||||
out = Buffer.concat([out.subarray(0, r.start), Buffer.from(r.text, 'utf8'), out.subarray(r.end)])
|
||||
lastStart = r.start
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in new issue
Block a user