|
| 1 | +script({ |
| 2 | + title: "Generate TypeScript function documentation using AST insertion", |
| 3 | + accept: ".ts", |
| 4 | + files: "src/cowsay.ts", |
| 5 | + parameters: { |
| 6 | + applyEdits: { |
| 7 | + type: "boolean", |
| 8 | + default: false, |
| 9 | + description: "If true, the script will not modify the files.", |
| 10 | + }, |
| 11 | + }, |
| 12 | +}) |
| 13 | +const { output } = env |
| 14 | +const { applyEdits } = env.vars |
| 15 | +const file = env.files[0] |
| 16 | + |
| 17 | +// find all exported functions with comments |
| 18 | +const sg = await host.astGrep() |
| 19 | +const { matches, replace, commitEdits } = await sg.search("ts", file.filename, { |
| 20 | + rule: { |
| 21 | + kind: "export_statement", |
| 22 | + follows: { |
| 23 | + kind: "comment", |
| 24 | + stopBy: "neighbor", |
| 25 | + }, |
| 26 | + has: { |
| 27 | + kind: "function_declaration", |
| 28 | + }, |
| 29 | + }, |
| 30 | +}) |
| 31 | + |
| 32 | +// for each match, generate a docstring for functions not documented |
| 33 | +for (const match of matches) { |
| 34 | + const comment = match.prev() |
| 35 | + |
| 36 | + const res = await runPrompt( |
| 37 | + (_) => { |
| 38 | + _.def("FILE", match.getRoot().root().text(), { flex: 1 }) |
| 39 | + _.def("DOCSTRING", comment.text(), { flex: 10 }) |
| 40 | + _.def("FUNCTION", match.text(), { flex: 10 }) |
| 41 | + // this needs more eval-ing |
| 42 | + _.$`Update the docstring <DOCSTRING> of function <FUNCTION>. |
| 43 | + - If the docstring is up to date, return <NOP>. |
| 44 | + - Make sure parameters are documented. |
| 45 | + - Be concise. Use technical tone. |
| 46 | + - do NOT include types, this is for TypeScript. |
| 47 | + - Use docstring syntax. |
| 48 | + - Minimize updates to the existing docstring. |
| 49 | + |
| 50 | + The full source of the file is in <FILE> for reference. |
| 51 | + The source of the function is in <FUNCTION>. |
| 52 | + The current docstring is <DOCSTRING>. |
| 53 | + ` |
| 54 | + }, |
| 55 | + { |
| 56 | + model: "small", |
| 57 | + responseType: "text", |
| 58 | + flexTokens: 12000, |
| 59 | + label: match.child(0).text(), |
| 60 | + } |
| 61 | + ) |
| 62 | + // if generation is successful, insert the docs |
| 63 | + if (res.error) { |
| 64 | + output.warn(res.error.message) |
| 65 | + continue |
| 66 | + } |
| 67 | + |
| 68 | + if (res.text.includes("<NOP>")) continue |
| 69 | + |
| 70 | + const docs = docify(res.text.trim()) |
| 71 | + replace(comment, docs) |
| 72 | +} |
| 73 | + |
| 74 | +// apply all edits and write to the file |
| 75 | +const modified = await commitEdits() |
| 76 | +if (applyEdits) { |
| 77 | + await workspace.writeFiles(modified) |
| 78 | +} else if (modified.length) { |
| 79 | + output.diff(file, modified[0]) |
| 80 | + output.warn( |
| 81 | + `edit not applied, use --vars 'applyEdits=true' to apply the edits` |
| 82 | + ) |
| 83 | +} |
| 84 | + |
| 85 | +// normalizes the docstring in case the LLM decides not to generate proper comments |
| 86 | +function docify(docs: string) { |
| 87 | + if (!/^\/\*\*.*.*\*\/$/s.test(docs)) |
| 88 | + docs = `/**\n* ${docs.split(/\r?\n/g).join("\n* ")}\n*/` |
| 89 | + return docs.replace(/\n+$/, "") |
| 90 | +} |
0 commit comments