Files
omp-ast/lib/sg.js
T

61 lines
2.6 KiB
JavaScript

// 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
}