{"format":1,"manifest":{"name":"doc-helpers","publisher":"vyloxi","version":"1.0.0","displayName":"Doc Helpers","description":"Write the doc comment for a function (JSDoc, docstring, Rustdoc, Javadoc, PHPDoc, KDoc, Go), add license headers and keep a changelog.","engines":{"ferrum":"^0.1.0"},"categories":["Tools"],"icon":"icon.png","repository":"https://github.com/voukengdongmofrankysteve/ferrum-market","license":"MIT","main":"extension.js","activationEvents":["onCommand:docHelpers.documentFunction","onCommand:docHelpers.insertLicense","onCommand:docHelpers.changelogEntry","onCommand:docHelpers.changelogRelease"],"permissions":[],"contributes":{"commands":[{"command":"docHelpers.documentFunction","title":"Document the Function Under Cursor","category":"Doc Helpers"},{"command":"docHelpers.insertLicense","title":"Insert License Header...","category":"Doc Helpers"},{"command":"docHelpers.changelogEntry","title":"Add Changelog Entry...","category":"Doc Helpers"},{"command":"docHelpers.changelogRelease","title":"Release the Unreleased Changelog Section...","category":"Doc Helpers"}],"keybindings":[{"command":"docHelpers.documentFunction","key":"Ctrl+Alt+D"}],"configuration":{"title":"Doc Helpers","properties":{"docHelpers.license":{"type":"string","enum":["MIT","Apache-2.0","GPL-3.0-or-later","BSD-3-Clause","ISC","MPL-2.0","Unlicense"],"default":"MIT","description":"The license offered first when you insert a header."},"docHelpers.holder":{"type":"string","default":"","description":"The copyright holder written in license headers. If empty you are asked."}}}}},"files":{"icon.png":{"encoding":"base64","content":"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"},"extension.js":{"encoding":"utf8","content":"// Doc Helpers: write the comment above a function (JSDoc, docstring, Rustdoc, Javadoc, PHPDoc, KDoc, Go), add a license\r\n// header to a file, and keep a CHANGELOG in the \"Keep a Changelog\" format. No permissions: it only edits the open file.\r\n\r\n// ---- signatures ------------------------------------------------------------------------------------------------------\r\n/** Splits a parameter list at top level commas, so generics and default values stay in one piece. */\r\nfunction splitParams(s) {\r\n  const out = [];\r\n  let depth = 0;\r\n  let cur = '';\r\n  let quote = '';\r\n  for (const c of s) {\r\n    if (quote) { cur += c; if (c === quote) quote = ''; continue; }\r\n    if (c === '\"' || c === \"'\" || c === '`') { quote = c; cur += c; continue; }\r\n    if ('([{<'.includes(c)) depth++;\r\n    else if (')]}>'.includes(c) && !(c === '>' && cur.endsWith('-'))) depth--;\r\n    if (c === ',' && depth === 0) { out.push(cur); cur = ''; } else cur += c;\r\n  }\r\n  if (cur.trim()) out.push(cur);\r\n  return out.map((p) => p.trim()).filter(Boolean);\r\n}\r\n\r\n/** Joins a signature that continues over several lines until its parentheses balance. */\r\nfunction gather(lines, i) {\r\n  let text = '';\r\n  let depth = 0;\r\n  let seen = false;\r\n  let end = i;\r\n  for (let k = i; k < Math.min(lines.length, i + 14); k++) {\r\n    text += (k > i ? ' ' : '') + lines[k].trim();\r\n    end = k;\r\n    for (const c of lines[k]) { if (c === '(') { depth++; seen = true; } else if (c === ')') depth--; }\r\n    if (seen && depth <= 0) break;\r\n  }\r\n  return { text, end };\r\n}\r\n\r\nconst JS_NAME = '[A-Za-z_$][\\\\w$]*';\r\nfunction parseJs(sig) {\r\n  let m = new RegExp('^(?:export\\\\s+)?(?:default\\\\s+)?(async\\\\s+)?function\\\\s*\\\\*?\\\\s*(' + JS_NAME + ')?\\\\s*(?:<[^>(]*>)?\\\\s*\\\\(').exec(sig);\r\n  let name;\r\n  let isAsync;\r\n  if (m) { name = m[2] || 'anonymous'; isAsync = !!m[1]; }\r\n  else if ((m = new RegExp('^(?:export\\\\s+)?(?:const|let|var)\\\\s+(' + JS_NAME + ')\\\\s*(?::[^=]+)?=\\\\s*(async\\\\s+)?(?:function\\\\s*\\\\*?\\\\s*' + JS_NAME + '?\\\\s*)?(?:<[^>(]*>)?\\\\s*\\\\(').exec(sig))) { name = m[1]; isAsync = !!m[2]; }\r\n  else if ((m = new RegExp('^(?:export\\\\s+)?(?:const|let|var)\\\\s+(' + JS_NAME + ')\\\\s*(?::[^=]+)?=\\\\s*(async\\\\s+)?(' + JS_NAME + ')\\\\s*=>').exec(sig))) return { name: m[1], params: [{ name: m[3] }], async: !!m[2] };\r\n  else if ((m = new RegExp('^(?:(?:public|private|protected|static|readonly|override|abstract)\\\\s+)*(async\\\\s+)?(?:get\\\\s+|set\\\\s+)?\\\\*?\\\\s*(' + JS_NAME + ')\\\\s*(?:<[^>(]*>)?\\\\s*\\\\(').exec(sig)) && !/^(if|for|while|switch|catch|return|function|constructor_)$/.test(m[2])) { name = m[2]; isAsync = !!m[1]; }\r\n  else if ((m = new RegExp('^(' + JS_NAME + ')\\\\s*[:=]\\\\s*(async\\\\s+)?(?:function\\\\s*)?\\\\(').exec(sig))) { name = m[1]; isAsync = !!m[2]; }\r\n  else return null;\r\n  const open = sig.indexOf('(', m.index + m[0].length - 1);\r\n  let depth = 0;\r\n  let close = -1;\r\n  for (let k = open; k < sig.length; k++) { if (sig[k] === '(') depth++; else if (sig[k] === ')') { depth--; if (!depth) { close = k; break; } } }\r\n  if (close < 0) return null;\r\n  const params = splitParams(sig.slice(open + 1, close)).map((p) => {\r\n    let rest = p.replace(/^(?:public|private|protected|readonly)\\s+/, '');\r\n    const spread = rest.startsWith('...');\r\n    rest = rest.replace(/^\\.\\.\\./, '');\r\n    let type;\r\n    let pname;\r\n    let optional = false;\r\n    if (/^[{[]/.test(rest)) {\r\n      const t = /:\\s*([^=]+?)\\s*(?:=.*)?$/.exec(rest.replace(/^[{[][^}\\]]*[}\\]]/, ''));\r\n      pname = rest[0] === '{' ? 'options' : 'items';\r\n      type = t ? t[1] : undefined;\r\n    } else {\r\n      const mm = /^([A-Za-z_$][\\w$]*)(\\?)?\\s*(?::\\s*([^=]+?))?\\s*(?:=\\s*(.+))?$/.exec(rest);\r\n      if (!mm) return { name: rest.split(/[=:]/)[0].trim() || 'arg' };\r\n      pname = mm[1];\r\n      type = mm[3];\r\n      optional = !!mm[2] || mm[4] !== undefined;\r\n    }\r\n    return { name: pname, type, optional, rest: spread };\r\n  });\r\n  const after = sig.slice(close + 1);\r\n  const rm = /^\\s*:\\s*([^{=]+?)\\s*(?:\\{|=>|$)/.exec(after);\r\n  return { name, params, async: isAsync, ret: rm ? rm[1].trim() : undefined };\r\n}\r\n\r\nfunction parsePython(sig) {\r\n  const m = /^(async\\s+)?def\\s+([A-Za-z_]\\w*)\\s*\\(/.exec(sig);\r\n  if (!m) return null;\r\n  const open = sig.indexOf('(');\r\n  let depth = 0;\r\n  let close = -1;\r\n  for (let k = open; k < sig.length; k++) { if (sig[k] === '(') depth++; else if (sig[k] === ')') { depth--; if (!depth) { close = k; break; } } }\r\n  if (close < 0) return null;\r\n  const params = [];\r\n  for (const p of splitParams(sig.slice(open + 1, close))) {\r\n    if (p === '/' || p === '*') continue;\r\n    const mm = /^(\\*{0,2})([A-Za-z_]\\w*)\\s*(?::\\s*([^=]+?))?\\s*(?:=\\s*(.+))?$/.exec(p);\r\n    if (!mm) continue;\r\n    if (['self', 'cls'].includes(mm[2]) && !params.length) continue;\r\n    params.push({ name: mm[1] + mm[2], type: mm[3], optional: mm[4] !== undefined });\r\n  }\r\n  const rm = /\\)\\s*->\\s*([^:]+?)\\s*:/.exec(sig.slice(close));\r\n  return { name: m[2], params, async: !!m[1], ret: rm ? rm[1].trim() : undefined };\r\n}\r\n\r\nfunction parseRust(sig) {\r\n  const m = /^(?:pub(?:\\([^)]*\\))?\\s+)?(?:const\\s+)?(async\\s+)?(?:unsafe\\s+)?(?:extern\\s+\"[^\"]*\"\\s+)?fn\\s+([A-Za-z_]\\w*)\\s*(?:<[^>]*(?:<[^>]*>[^>]*)*>)?\\s*\\(/.exec(sig);\r\n  if (!m) return null;\r\n  const open = sig.indexOf('(', m.index + m[0].length - 1);\r\n  let depth = 0;\r\n  let close = -1;\r\n  for (let k = open; k < sig.length; k++) { if (sig[k] === '(') depth++; else if (sig[k] === ')') { depth--; if (!depth) { close = k; break; } } }\r\n  if (close < 0) return null;\r\n  const params = [];\r\n  for (const p of splitParams(sig.slice(open + 1, close))) {\r\n    if (/^&?(?:'\\w+\\s+)?(?:mut\\s+)?self\\b/.test(p) || /^self\\s*:/.test(p) || /^mut\\s+self/.test(p)) continue;\r\n    const mm = /^(?:mut\\s+)?([A-Za-z_]\\w*)\\s*:\\s*(.+)$/.exec(p);\r\n    if (mm) params.push({ name: mm[1], type: mm[2].trim() });\r\n  }\r\n  const rm = /->\\s*([^{]+?)\\s*(?:where\\b.*)?\\{?\\s*$/.exec(sig.slice(close));\r\n  return { name: m[2], params, async: !!m[1], ret: rm ? rm[1].trim() : undefined };\r\n}\r\n\r\nfunction parseGo(sig) {\r\n  const m = /^func\\s*(?:\\(\\s*\\w*\\s*\\*?\\s*[\\w.\\[\\]]+\\s*\\)\\s*)?([A-Za-z_]\\w*)\\s*(?:\\[[^\\]]*\\])?\\s*\\(/.exec(sig);\r\n  if (!m) return null;\r\n  const open = sig.indexOf('(', m.index + m[0].length - 1);\r\n  let depth = 0;\r\n  let close = -1;\r\n  for (let k = open; k < sig.length; k++) { if (sig[k] === '(') depth++; else if (sig[k] === ')') { depth--; if (!depth) { close = k; break; } } }\r\n  if (close < 0) return null;\r\n  const params = [];\r\n  let pending = [];\r\n  for (const p of splitParams(sig.slice(open + 1, close))) {\r\n    const mm = /^([A-Za-z_]\\w*)\\s+(.+)$/.exec(p);\r\n    if (mm) { for (const n of pending) params.push({ name: n, type: mm[2] }); pending = []; params.push({ name: mm[1], type: mm[2] }); } else if (/^[A-Za-z_]\\w*$/.test(p)) pending.push(p);\r\n  }\r\n  const rm = /^\\s*(\\([^)]*\\)|[^{\\s][^{]*?)\\s*\\{?\\s*$/.exec(sig.slice(close + 1));\r\n  return { name: m[1], params, ret: rm && rm[1] ? rm[1].trim() : undefined };\r\n}\r\n\r\nfunction parseCLike(sig, lang) {\r\n  let m;\r\n  let name;\r\n  let ret;\r\n  let open;\r\n  if (lang === 'php') {\r\n    m = /^(?:(?:public|private|protected|static|final|abstract)\\s+)*function\\s+&?\\s*([A-Za-z_]\\w*)\\s*\\(/.exec(sig);\r\n    if (!m) return null;\r\n    name = m[1];\r\n  } else if (lang === 'kotlin') {\r\n    m = /^(?:(?:public|private|protected|internal|override|open|abstract|suspend|inline|operator)\\s+)*fun\\s+(?:<[^>]*>\\s*)?(?:[\\w.<>?]+\\.)?([A-Za-z_]\\w*)\\s*\\(/.exec(sig);\r\n    if (!m) return null;\r\n    name = m[1];\r\n  } else {\r\n    m = /^(?:(?:public|private|protected|internal|static|final|abstract|virtual|override|async|sealed|extern|inline|constexpr|unsafe|synchronized|native|export)\\s+)*(?:<[^>]*>\\s*)?([\\w.<>,\\[\\]?*&:\\s]+?)\\s+([A-Za-z_]\\w*)\\s*\\(/.exec(sig);\r\n    if (!m || /^(if|for|while|switch|catch|return|else|new|throw)$/.test(m[2]) || /^(return|new|throw|else)\\b/.test(m[1])) return null;\r\n    name = m[2];\r\n    ret = m[1].trim();\r\n  }\r\n  open = sig.indexOf('(', m.index + m[0].length - 1);\r\n  let depth = 0;\r\n  let close = -1;\r\n  for (let k = open; k < sig.length; k++) { if (sig[k] === '(') depth++; else if (sig[k] === ')') { depth--; if (!depth) { close = k; break; } } }\r\n  if (close < 0) return null;\r\n  const params = [];\r\n  for (const p of splitParams(sig.slice(open + 1, close))) {\r\n    const clean = p.replace(/@\\w+(\\([^)]*\\))?\\s*/g, '').replace(/\\b(?:final|readonly|ref|out|in|params|this)\\s+/g, '').replace(/\\s*=\\s*.+$/, '').trim();\r\n    let mm;\r\n    if (lang === 'php') mm = /^(?:(\\??[\\w\\\\|]+)\\s+)?&?(\\.\\.\\.)?\\$([A-Za-z_]\\w*)$/.exec(clean);\r\n    else if (lang === 'kotlin') mm = /^(?:vararg\\s+)?([A-Za-z_]\\w*)\\s*:\\s*(.+)$/.exec(clean);\r\n    else mm = /^(.+?)\\s+([A-Za-z_]\\w*)$/.exec(clean);\r\n    if (!mm) continue;\r\n    if (lang === 'php') params.push({ name: '$' + mm[3], type: mm[1] });\r\n    else if (lang === 'kotlin') params.push({ name: mm[1], type: mm[2] });\r\n    else params.push({ name: mm[2], type: mm[1] });\r\n  }\r\n  if (lang === 'php') { const rm = /\\)\\s*:\\s*(\\??[\\w\\\\|]+)/.exec(sig.slice(close)); ret = rm ? rm[1] : undefined; }\r\n  if (lang === 'kotlin') { const rm = /\\)\\s*:\\s*([^{=]+?)\\s*(?:\\{|=|$)/.exec(sig.slice(close)); ret = rm ? rm[1].trim() : undefined; }\r\n  return { name, params, ret };\r\n}\r\n\r\nconst FAMILY = {\r\n  JavaScript: 'js', TypeScript: 'js', Python: 'py', Rust: 'rust', Go: 'go', 'Java / C#': 'java', 'C / C++': 'java', PHP: 'php', Kotlin: 'kotlin', Dart: 'java', Scala: 'java',\r\n};\r\nconst EXT = { js: 'js', jsx: 'js', mjs: 'js', cjs: 'js', ts: 'js', tsx: 'js', py: 'py', rs: 'rust', go: 'go', java: 'java', cs: 'java', c: 'java', cpp: 'java', h: 'java', hpp: 'java', php: 'php', kt: 'kotlin', kts: 'kotlin', dart: 'java', scala: 'java' };\r\nfunction familyOf(language, name) {\r\n  const ext = String(name || '').split('.').pop().toLowerCase();\r\n  return FAMILY[language] || EXT[ext] || null;\r\n}\r\nconst isTs = (language, name) => language === 'TypeScript' || /\\.tsx?$/i.test(name || '');\r\n\r\nfunction parseSignature(family, sig) {\r\n  if (family === 'js') return parseJs(sig);\r\n  if (family === 'py') return parsePython(sig);\r\n  if (family === 'rust') return parseRust(sig);\r\n  if (family === 'go') return parseGo(sig);\r\n  if (family === 'java' || family === 'php' || family === 'kotlin') return parseCLike(sig, family);\r\n  return null;\r\n}\r\n\r\n// ---- comments --------------------------------------------------------------------------------------------------------\r\n/**\r\n * Builds the doc comment. Returns { text, where } where `where` is 'above' (before the signature) or 'below' (inside the body, Python).\r\n * `indent` is the indentation of the signature line. Descriptions are left empty for you to fill in.\r\n */\r\nfunction buildDoc(family, sig, indent, { ts = false, unit = '    ' } = {}) {\r\n  const ps = sig.params;\r\n  const returns = sig.ret && !/^(void|None|\\(\\)|Unit)$/.test(sig.ret) ? sig.ret : null;\r\n  if (family === 'js') {\r\n    const lines = ['/**', ' * '];\r\n    for (const p of ps) lines.push(' * @param ' + (ts || !p.type ? '' : '{' + p.type + '} ') + (p.optional && !ts ? '[' + p.name + ']' : p.name) + ' - ');\r\n    if (returns || sig.async) lines.push(' * @returns ' + (ts ? '' : '{' + (sig.async && (!returns || !/Promise/.test(returns)) ? 'Promise<' + (returns || '*') + '>' : returns || '*') + '} '));\r\n    lines.push(' */');\r\n    return { text: lines.map((l) => indent + l).join('\\n') + '\\n', where: 'above' };\r\n  }\r\n  if (family === 'py') {\r\n    const inner = indent + unit;\r\n    const lines = [inner + '\"\"\"', inner];\r\n    if (ps.length) {\r\n      lines.push('', inner + 'Args:');\r\n      for (const p of ps) lines.push(inner + unit + p.name + (p.type ? ' (' + p.type.trim() + (p.optional ? ', optional' : '') + ')' : '') + ': ');\r\n    }\r\n    if (returns) lines.push('', inner + 'Returns:', inner + unit + returns + ': ');\r\n    lines.push(inner + '\"\"\"');\r\n    return { text: lines.join('\\n') + '\\n', where: 'below' };\r\n  }\r\n  if (family === 'rust') {\r\n    const lines = ['/// '];\r\n    if (ps.length) { lines.push('///', '/// # Arguments', '///'); for (const p of ps) lines.push('/// * `' + p.name + '` - '); }\r\n    if (returns && /^Result\\b/.test(returns)) lines.push('///', '/// # Errors', '///', '/// Returns an error when ');\r\n    else if (returns) lines.push('///', '/// # Returns', '///', '/// ');\r\n    return { text: lines.map((l) => indent + l).join('\\n') + '\\n', where: 'above' };\r\n  }\r\n  if (family === 'go') {\r\n    return { text: indent + '// ' + sig.name + ' ' + '\\n', where: 'above' };\r\n  }\r\n  if (family === 'php' || family === 'java' || family === 'kotlin') {\r\n    const lines = ['/**', ' * '];\r\n    if (ps.length || returns) lines.push(' *');\r\n    for (const p of ps) lines.push(family === 'kotlin' ? ' * @param ' + p.name + ' ' : family === 'php' ? ' * @param ' + (p.type ? p.type + ' ' : 'mixed ') + p.name + ' ' : ' * @param ' + p.name + ' ');\r\n    if (returns) lines.push(family === 'php' ? ' * @return ' + returns + ' ' : ' * @return ');\r\n    lines.push(' */');\r\n    return { text: lines.map((l) => indent + l).join('\\n') + '\\n', where: 'above' };\r\n  }\r\n  return null;\r\n}\r\n\r\n// ---- license headers -------------------------------------------------------------------------------------------------\r\nconst LICENSES = {\r\n  'MIT': (y, h) => [`Copyright (c) ${y} ${h}`, '', 'Licensed under the MIT License. See the LICENSE file in the project root for the full text.', '', 'SPDX-License-Identifier: MIT'],\r\n  'Apache-2.0': (y, h) => [`Copyright ${y} ${h}`, '', 'Licensed under the Apache License, Version 2.0 (the \"License\");', 'you may not use this file except in compliance with the License.', 'You may obtain a copy of the License at', '', '    http://www.apache.org/licenses/LICENSE-2.0', '', 'Unless required by applicable law or agreed to in writing, software', 'distributed under the License is distributed on an \"AS IS\" BASIS,', 'WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.', 'See the License for the specific language governing permissions and', 'limitations under the License.', '', 'SPDX-License-Identifier: Apache-2.0'],\r\n  'GPL-3.0-or-later': (y, h) => [`Copyright (C) ${y} ${h}`, '', 'This program is free software: you can redistribute it and/or modify', 'it under the terms of the GNU General Public License as published by', 'the Free Software Foundation, either version 3 of the License, or', '(at your option) any later version.', '', 'This program is distributed in the hope that it will be useful,', 'but WITHOUT ANY WARRANTY; without even the implied warranty of', 'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the', 'GNU General Public License for more details.', '', 'You should have received a copy of the GNU General Public License', 'along with this program.  If not, see <https://www.gnu.org/licenses/>.', '', 'SPDX-License-Identifier: GPL-3.0-or-later'],\r\n  'BSD-3-Clause': (y, h) => [`Copyright (c) ${y}, ${h}. All rights reserved.`, '', 'Use of this source code is governed by a BSD-style license that can be', 'found in the LICENSE file.', '', 'SPDX-License-Identifier: BSD-3-Clause'],\r\n  'ISC': (y, h) => [`Copyright (c) ${y}, ${h}`, '', 'Permission to use, copy, modify, and/or distribute this software for any', 'purpose with or without fee is hereby granted, provided that the above', 'copyright notice and this permission notice appear in all copies.', '', 'SPDX-License-Identifier: ISC'],\r\n  'MPL-2.0': (y, h) => [`Copyright (c) ${y} ${h}`, '', 'This Source Code Form is subject to the terms of the Mozilla Public', 'License, v. 2.0. If a copy of the MPL was not distributed with this', 'file, You can obtain one at https://mozilla.org/MPL/2.0/.', '', 'SPDX-License-Identifier: MPL-2.0'],\r\n  'Unlicense': (y, h) => [`Written in ${y} by ${h}.`, '', 'This is free and unencumbered software released into the public domain.', 'See https://unlicense.org for details.', '', 'SPDX-License-Identifier: Unlicense'],\r\n};\r\n\r\nconst COMMENTS = {\r\n  line: { '//': 'js ts jsx tsx mjs cjs java kt kts c cpp cc h hpp cs go rs swift dart scala php groovy gradle zig v sol proto', '#': 'py rb sh bash zsh pl pm r yml yaml toml tf ex exs cr nim jl ps1 mk cmake dockerfile', '--': 'sql lua hs elm', ';': 'lisp clj cl el asm', '%': 'tex erl', '\"': 'vim' },\r\n  block: { '/* */': 'css scss less', '<!-- -->': 'html htm xml svg vue svelte md' },\r\n};\r\nfunction commentStyle(name) {\r\n  const ext = String(name || '').split('.').pop().toLowerCase();\r\n  for (const [k, v] of Object.entries(COMMENTS.line)) if (v.split(' ').includes(ext)) return { line: k };\r\n  for (const [k, v] of Object.entries(COMMENTS.block)) if (v.split(' ').includes(ext)) { const [a, b] = k.split(' '); return { block: [a, b] }; }\r\n  return null;\r\n}\r\n\r\n/** The header as comment text for the file name, or null when the file type has no known comment syntax. */\r\nfunction licenseHeader(name, license, year, holder) {\r\n  const body = LICENSES[license](year, holder);\r\n  const style = commentStyle(name);\r\n  if (!style) return null;\r\n  if (style.line) return body.map((l) => (l ? style.line + ' ' + l : style.line)).join('\\n') + '\\n\\n';\r\n  return style.block[0] + '\\n' + body.map((l) => (l ? '  ' + l : '')).join('\\n') + '\\n' + style.block[1] + '\\n\\n';\r\n}\r\n\r\n/** Where a header goes: after a shebang, <?php or <?xml line, otherwise at the top. */\r\nfunction headerLine(text) {\r\n  const first = text.split('\\n', 1)[0];\r\n  return /^(#!|<\\?php|<\\?xml|<!doctype)/i.test(first) ? 1 : 0;\r\n}\r\nconst hasHeader = (text) => /copyright|spdx-license|licensed under/i.test(text.split('\\n').slice(0, 14).join('\\n'));\r\n\r\n// ---- changelog -------------------------------------------------------------------------------------------------------\r\nconst ORDER = ['Added', 'Changed', 'Deprecated', 'Removed', 'Fixed', 'Security'];\r\n\r\n/** Adds `- entry` under `### type` of the Unreleased section (created when missing). */\r\nfunction addEntry(text, type, entry) {\r\n  const nl = text.includes('\\r\\n') ? '\\r\\n' : '\\n';\r\n  const lines = text.replace(/\\r\\n/g, '\\n').split('\\n');\r\n  let u = lines.findIndex((l) => /^##\\s+\\[?unreleased\\]?/i.test(l));\r\n  if (u < 0) {\r\n    const firstVersion = lines.findIndex((l) => /^##\\s+/.test(l));\r\n    const title = lines.findIndex((l) => /^#\\s+/.test(l));\r\n    const at = firstVersion >= 0 ? firstVersion : title >= 0 ? title + 1 : lines.length;\r\n    const block = ['## [Unreleased]', ''];\r\n    if (at > 0 && lines[at - 1].trim() !== '') block.unshift('');\r\n    lines.splice(at, 0, ...block);\r\n    u = lines.findIndex((l) => /^##\\s+\\[?unreleased\\]?/i.test(l));\r\n  }\r\n  let end = lines.findIndex((l, i) => i > u && /^##\\s+/.test(l));\r\n  if (end < 0) end = lines.length;\r\n  const heading = '### ' + type;\r\n  let h = -1;\r\n  for (let i = u + 1; i < end; i++) if (lines[i].trim().toLowerCase() === heading.toLowerCase()) { h = i; break; }\r\n  if (h >= 0) {\r\n    let last = h;\r\n    for (let i = h + 1; i < end && !/^###\\s+/.test(lines[i]); i++) if (lines[i].trim()) last = i;\r\n    lines.splice(last + 1, 0, '- ' + entry);\r\n  } else {\r\n    // Keep the standard order of sections: before the first existing section that should come after this one.\r\n    const rank = ORDER.indexOf(type);\r\n    let before = -1;\r\n    for (let i = u + 1; i < end; i++) {\r\n      const m = /^###\\s+(.+?)\\s*$/.exec(lines[i]);\r\n      if (m && ORDER.indexOf(m[1]) > rank) { before = i; break; }\r\n    }\r\n    if (before >= 0) lines.splice(before, 0, heading, '', '- ' + entry, '');\r\n    else {\r\n      let last = u;\r\n      for (let i = u + 1; i < end; i++) if (lines[i].trim()) last = i;\r\n      lines.splice(last + 1, 0, '', heading, '', '- ' + entry);\r\n      const next = last + 5;\r\n      if (next < lines.length && lines[next].trim() !== '') lines.splice(next, 0, '');\r\n    }\r\n  }\r\n  // End the file with exactly one newline when it ended with one before.\r\n  if (text.endsWith('\\n')) { while (lines.length > 1 && lines[lines.length - 1] === '' && lines[lines.length - 2] === '') lines.pop(); }\r\n  return lines.join(nl);\r\n}\r\n\r\n/** Turns the Unreleased section into a released version and leaves a fresh empty Unreleased on top. */\r\nfunction release(text, version, date) {\r\n  const nl = text.includes('\\r\\n') ? '\\r\\n' : '\\n';\r\n  const lines = text.replace(/\\r\\n/g, '\\n').split('\\n');\r\n  const u = lines.findIndex((l) => /^##\\s+\\[?unreleased\\]?/i.test(l));\r\n  if (u < 0) throw new Error('There is no Unreleased section.');\r\n  lines[u] = '## [' + version + '] - ' + date;\r\n  lines.splice(u, 0, '## [Unreleased]', '');\r\n  return lines.join(nl);\r\n}\r\n\r\nexports._test = { splitParams, gather, parseSignature, buildDoc, familyOf, licenseHeader, headerLine, hasHeader, commentStyle, addEntry, release, LICENSES };\r\n\r\nexports.activate = function (context) {\r\n  const reg = (id, fn) => context.subscriptions.push(ferrum.commands.register(id, fn));\r\n  const today = () => new Date().toISOString().slice(0, 10);\r\n\r\n  reg('docHelpers.documentFunction', async () => {\r\n    const doc = await ferrum.editor.active();\r\n    if (!doc) return ferrum.window.showMessage('Open a file first.', 'warn');\r\n    const family = familyOf(doc.language, doc.name);\r\n    if (!family) return ferrum.window.showMessage('Doc comments are not supported for ' + (doc.language || doc.name) + ' yet.', 'warn');\r\n    const lines = doc.text.split('\\n');\r\n    const at = doc.selection.start.line;\r\n    let found = null;\r\n    for (let i = at; i < Math.min(lines.length, at + 4) && !found; i++) {\r\n      if (!lines[i].trim()) continue;\r\n      const g = gather(lines, i);\r\n      const sig = parseSignature(family, g.text);\r\n      if (sig) found = { i, g, sig };\r\n    }\r\n    if (!found) return ferrum.window.showMessage('Put the cursor on a function or method signature.', 'warn');\r\n    const indent = /^\\s*/.exec(lines[found.i])[0];\r\n    const unit = indent.includes('\\t') ? '\\t' : '    ';\r\n    const built = buildDoc(family, found.sig, indent, { ts: isTs(doc.language, doc.name), unit });\r\n    if (!built) return;\r\n    const row = built.where === 'above' ? found.i : found.g.end + 1;\r\n    if (built.where === 'above' && found.i > 0 && /^\\s*(\\/\\*\\*|\\/\\/\\/|\\*\\/|\\/\\/ )/.test(lines[found.i - 1])) {\r\n      if (!(await ferrum.window.confirm('There already is a comment above this function. Add another one?', 'Add'))) return;\r\n    }\r\n    await ferrum.editor.replace({ start: { line: row, col: 0 }, end: { line: row, col: 0 } }, built.text);\r\n    // Put the cursor at the end of the summary line so you can start typing.\r\n    const idx = family === 'rust' || family === 'go' ? 0 : 1;\r\n    await ferrum.editor.setSelection({ line: row + idx, col: built.text.split('\\n')[idx].length });\r\n  });\r\n\r\n  reg('docHelpers.insertLicense', async () => {\r\n    const doc = await ferrum.editor.active();\r\n    if (!doc) return ferrum.window.showMessage('Open a file first.', 'warn');\r\n    if (!commentStyle(doc.name)) return ferrum.window.showMessage('I do not know the comment syntax for ' + doc.name + '.', 'warn');\r\n    const names = Object.keys(LICENSES);\r\n    const def = String(ferrum.settings.get('docHelpers.license') || 'MIT');\r\n    const pick = await ferrum.window.showQuickPick([...names].sort((a, b) => (a === def ? -1 : b === def ? 1 : 0)).map((n) => ({ label: n, detail: n === def ? 'Your default' : '' })), { placeholder: 'Which license header?' });\r\n    if (!pick) return;\r\n    let holder = String(ferrum.settings.get('docHelpers.holder') || '').trim();\r\n    if (!holder) {\r\n      holder = ((await ferrum.window.showInputBox({ prompt: 'Copyright holder (you can set it once in the settings)', placeholder: 'Your Name or Company' })) || '').trim();\r\n      if (!holder) return;\r\n    }\r\n    const header = licenseHeader(doc.name, pick.label, new Date().getFullYear(), holder);\r\n    if (hasHeader(doc.text) && !(await ferrum.window.confirm('This file already starts with a copyright or license notice. Add another header?', 'Add'))) return;\r\n    const row = headerLine(doc.text);\r\n    await ferrum.editor.replace({ start: { line: row, col: 0 }, end: { line: row, col: 0 } }, (row ? '\\n' : '') + header);\r\n  });\r\n\r\n  reg('docHelpers.changelogEntry', async () => {\r\n    const doc = await ferrum.editor.active();\r\n    if (!doc) return ferrum.window.showMessage('Open your CHANGELOG.md first.', 'warn');\r\n    if (!/\\.md$/i.test(doc.name)) return ferrum.window.showMessage('Open your CHANGELOG.md first, then run this again.', 'warn');\r\n    const type = await ferrum.window.showQuickPick(ORDER.map((t) => ({ label: t, detail: { Added: 'New features', Changed: 'Changes to what exists', Deprecated: 'Soon to be removed', Removed: 'Removed features', Fixed: 'Bug fixes', Security: 'Vulnerabilities' }[t] })), { placeholder: 'What kind of change?' });\r\n    if (!type) return;\r\n    const entry = ((await ferrum.window.showInputBox({ prompt: type.label + ': describe the change in one line' })) || '').trim();\r\n    if (!entry) return;\r\n    const next = addEntry(doc.text, type.label, entry);\r\n    const all = doc.text.split('\\n');\r\n    await ferrum.editor.replace({ start: { line: 0, col: 0 }, end: { line: all.length - 1, col: all[all.length - 1].length } }, next);\r\n  });\r\n\r\n  reg('docHelpers.changelogRelease', async () => {\r\n    const doc = await ferrum.editor.active();\r\n    if (!doc || !/\\.md$/i.test(doc.name)) return ferrum.window.showMessage('Open your CHANGELOG.md first.', 'warn');\r\n    const version = ((await ferrum.window.showInputBox({ prompt: 'Version to release', placeholder: '1.2.0' })) || '').trim().replace(/^v/, '');\r\n    if (!/^\\d+\\.\\d+\\.\\d+([-+][\\w.]+)?$/.test(version)) return version ? ferrum.window.showMessage('Use a version like 1.2.0.', 'warn') : undefined;\r\n    try {\r\n      const next = release(doc.text, version, today());\r\n      const all = doc.text.split('\\n');\r\n      await ferrum.editor.replace({ start: { line: 0, col: 0 }, end: { line: all.length - 1, col: all[all.length - 1].length } }, next);\r\n    } catch (e) {\r\n      ferrum.window.showMessage(e.message, 'warn');\r\n    }\r\n  });\r\n};\r\n\r\nexports.deactivate = function () {};\r\n"},"README.md":{"encoding":"utf8","content":"# Doc Helpers\n\n## Document a function\n\nPut the cursor on (or just above) a function and run **Document the Function Under Cursor**. It reads the signature and writes the comment skeleton with every parameter and the return type, ready for you to fill in:\n\n| Language | Comment |\n| --- | --- |\n| JavaScript, TypeScript | JSDoc, or TSDoc (no types repeated) in TypeScript |\n| Python | Google style docstring inside the function |\n| Rust | `///` with Arguments, Returns or Errors |\n| Go | `// Name ...` |\n| Java, C#, C, C++, Dart, Scala | Javadoc style |\n| PHP | PHPDoc |\n| Kotlin | KDoc |\n\nIt handles signatures over several lines, `async`, generics, default values and destructured parameters. The summary lines are left empty on purpose: it never invents a description.\n\n## License headers\n\n**Insert License Header...** adds a short notice (MIT, Apache-2.0, GPL-3.0-or-later, BSD-3-Clause, ISC, MPL-2.0 or Unlicense) with your name and the year, as comments in the right syntax for the file. It goes after a shebang, `<?php` or XML declaration, and it asks before adding a second header. Remember to also have the full license in a LICENSE file.\n\n## Changelog\n\nFor a changelog in the [Keep a Changelog](https://keepachangelog.com) format: **Add Changelog Entry...** puts a bullet under *Unreleased* in the right section (Added, Changed, Deprecated, Removed, Fixed, Security), creating the section in the standard order. **Release the Unreleased Changelog Section...** turns *Unreleased* into a version with today's date and leaves a fresh empty *Unreleased* on top.\n\nNo permissions needed: it edits the file you have open.\n"}}}