#!/usr/bin/env node 'use strict'; /** * Add JavaDoc to workflow subpackages. Documentation-only; no logic changes. * Safe mode: only inserts missing javadoc or adds 锟斤拷锟矫凤拷 line to existing blocks. */ const fs = require('fs'); const path = require('path'); const ROOT = 'd:/ylrz_saas_new/java'; const WORKFLOW_DIR = path.join(ROOT, 'fs-service/src/main/java/com/fs/company/service/workflow'); const DATA = JSON.parse(fs.readFileSync(path.join(ROOT, 'scripts/workflow-subpkg-javadoc-data.json'), 'utf8')); const TARGET_PACKAGES = [ 'api', 'async', 'cache', 'canvas', 'capability', 'data', 'dedup', 'event', 'expression', 'feedback', 'handoff', 'health', 'heartbeat', 'http', 'inbound', 'knowledge', 'media', 'memory', 'monitor', 'pay', 'push', 'queue', 'scope', 'sim', 'vector', ]; const COLON = '\uFF1A'; const PERIOD = '\u3002'; const ENUM_SEP = '\u3001'; const CALLER = DATA.callerLabel || '\u8c03\u7528\u65b9'; const MAIN_CALLER = DATA.mainCallerLabel || '\u4e3b\u8981\u8c03\u7528\u65b9'; const SPRING = DATA.springCaller || 'Spring \u5bb9\u5668\u6ce8\u5165'; function getCallers(className) { if (DATA.callers && DATA.callers[className]) return DATA.callers[className]; const keys = [className, className.replace(/Impl$/, '')]; if (!keys[1].startsWith('I')) keys.push('I' + keys[1]); for (const k of keys) { if (DATA.callers && DATA.callers[k]) return DATA.callers[k]; } return [SPRING]; } function formatCallers(callers) { return callers.map(c => `{@code ${c}}`).join(ENUM_SEP); } function parseInterfaceMethods(className) { const iface = className.replace(/Impl$/, ''); const candidates = [ path.join(WORKFLOW_DIR, iface + '.java'), path.join(WORKFLOW_DIR, 'I' + iface + '.java'), ]; const methods = {}; for (const f of candidates) { if (!fs.existsSync(f)) continue; const text = fs.readFileSync(f, 'utf8'); const re = /\/\*\*([\s\S]*?)\*\/\s*(?:@[\w.]+\s*)*[\w<>,\[\]\s.?]+\s+(\w+)\s*\([^)]*\)\s*;/g; let m; while ((m = re.exec(text))) { const first = m[1].split('\n').map(l => l.replace(/^\s*\*\s?/, '').trim()) .find(l => l && !l.startsWith('@') && !l.startsWith('param') && !l.startsWith('return') && !l.includes(CALLER) && !l.includes(MAIN_CALLER)); if (first) methods[m[2]] = first.replace(/\s*@param.*/, '').trim(); } } return methods; } function methodPurpose(className, methodName, ifaceMethods) { if (ifaceMethods[methodName]) return ifaceMethods[methodName]; if (DATA.methodCn && DATA.methodCn[methodName]) return DATA.methodCn[methodName]; if (methodName.startsWith('get') && methodName.length > 3) return `\u83b7\u53d6${methodName.slice(3)}\u3002`; if (methodName.startsWith('set') && methodName.length > 3) return `\u8bbe\u7f6e${methodName.slice(3)}\u3002`; if (methodName.startsWith('is') && methodName.length > 2) return `\u5224\u65ad\u662f\u5426${methodName.slice(2)}\u3002`; if (methodName.startsWith('has') && methodName.length > 3) return `\u5224\u65ad\u662f\u5426\u5305\u542b${methodName.slice(3)}\u3002`; if (methodName.startsWith('list')) return `\u67e5\u8be2${methodName.slice(4)}\u5217\u8868\u3002`; if (methodName.startsWith('save') || methodName.startsWith('create')) return '\u4fdd\u5b58/\u521b\u5efa\u76f8\u5173\u6570\u636e\u3002'; if (methodName.startsWith('delete') || methodName.startsWith('remove')) return '\u5220\u9664\u76f8\u5173\u6570\u636e\u3002'; if (methodName.startsWith('update')) return `\u66f4\u65b0${methodName.slice(6)}\u3002`; return `${methodName} \u7684\u4e1a\u52a1\u5165\u53e3\u3002`; } function hasJavadocBefore(lines, idx) { let j = idx - 1; while (j >= 0 && (lines[j].trim().startsWith('@') || lines[j].trim() === '')) j--; return j >= 0 && lines[j].trim().endsWith('*/'); } function hasCallerInJavadoc(lines, idx) { let j = idx - 1; while (j >= 0) { const t = lines[j].trim(); if (t.startsWith('/**')) break; if (t.includes(CALLER) || t.includes(MAIN_CALLER)) return true; j--; } return false; } function makeMethodJavadoc(className, methodName, ifaceMethods, indent) { const purpose = methodPurpose(className, methodName, ifaceMethods); const callers = formatCallers(getCallers(className)); return [ `${indent}/**`, `${indent} * ${purpose}`, `${indent} *
${CALLER}${COLON}${callers}${PERIOD}`, `${indent} */`, ]; } function makeClassJavadoc(className, isInterface) { const kind = isInterface ? '\u63a5\u53e3' : '\u670d\u52a1'; const desc = (DATA.classDesc && DATA.classDesc[className]) || `${className}\uFF1A\u9f99\u867e workflow ${kind}\u3002`; const callers = formatCallers(getCallers(className)); return ['/**', ` * ${desc}`, ` *
${MAIN_CALLER}${COLON}${callers}${PERIOD}`, ' */']; } function extractMethodName(lines, startIdx) { let combined = lines[startIdx].trim(); let k = startIdx; while (!/[;{]/.test(combined) && k + 1 < lines.length) { k++; combined += ' ' + lines[k].trim(); } const mm = combined.match(/\b(\w+)\s*\(/); if (!mm || ['if', 'for', 'while', 'switch', 'catch', 'synchronized', 'return', 'new', 'super'].includes(mm[1])) return null; return mm[1]; } function findDeclIdx(lines, className) { return lines.findIndex(l => new RegExp(`public\\s+(?:final\\s+)?(?:class|interface|enum)\\s+${className}\\b`).test(l)); } function processFile(filePath) { const className = path.basename(filePath, '.java'); if (className === 'package-info') return false; let lines = fs.readFileSync(filePath, 'utf8').split(/\n/); const ifaceMethods = parseInterfaceMethods(className); const declIdx = findDeclIdx(lines, className); if (declIdx < 0) return false; const isInterface = /public\s+interface\s+/.test(lines[declIdx]); let modified = false; let hasClassDoc = false; { let j = declIdx - 1; while (j >= 0 && (lines[j].trim().startsWith('@') || lines[j].trim() === '')) j--; if (j >= 0 && (lines[j].trim().startsWith('/**') || lines[j].trim().endsWith('*/'))) hasClassDoc = true; } if (!hasClassDoc) { let insertAt = declIdx; while (insertAt > 0 && (lines[insertAt - 1].trim().startsWith('@') || lines[insertAt - 1].trim() === '')) insertAt--; const classDoc = makeClassJavadoc(className, isInterface); lines.splice(insertAt, 0, ...classDoc); modified = true; } else if (!hasCallerInJavadoc(lines, declIdx)) { let j = declIdx - 1; while (j >= 0 && (lines[j].trim().startsWith('@') || lines[j].trim() === '')) j--; if (j >= 0 && lines[j].trim() === '*/') { const callers = formatCallers(getCallers(className)); lines.splice(j, 0, ` *
${MAIN_CALLER}${COLON}${callers}${PERIOD}`); modified = true; } } const out = []; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const stripped = line.trim(); if (stripped.startsWith('@') && i + 1 < lines.length) { let k = i; while (k < lines.length && lines[k].trim().startsWith('@')) k++; if (k < lines.length && /^public\s+/.test(lines[k].trim())) { if (!hasJavadocBefore(lines, i)) { const name = extractMethodName(lines, k); if (name) { const indent = (line.match(/^(\s*)/) || ['', ' '])[1]; out.push(...makeMethodJavadoc(className, name, ifaceMethods, indent)); modified = true; } } else if (!hasCallerInJavadoc(lines, i)) { let j = i - 1; while (j >= 0 && lines[j].trim() !== '*/') j--; if (j >= 0) { const indent = (lines[j].match(/^(\s*)/) || ['', ' '])[1]; out.push(`${indent} *
${CALLER}${COLON}${formatCallers(getCallers(className))}${PERIOD}`); modified = true; } } while (i <= k) { out.push(lines[i]); i++; } i--; continue; } } if (/^public\s+(?!class\b|interface\b|enum\b|static\s+class\b)/.test(stripped)) { let prev = out.length - 1; while (prev >= 0 && out[prev].trim() === '') prev--; if (prev >= 0 && out[prev].trim().startsWith('@')) { out.push(line); continue; } if (!hasJavadocBefore(lines, i)) { const name = extractMethodName(lines, i); if (name) { const indent = (line.match(/^(\s*)/) || ['', ' '])[1]; out.push(...makeMethodJavadoc(className, name, ifaceMethods, indent)); modified = true; } } else if (!hasCallerInJavadoc(lines, i)) { let j = i - 1; while (j >= 0 && lines[j].trim() !== '*/') j--; if (j >= 0) { const indent = (lines[j].match(/^(\s*)/) || ['', ' '])[1]; out.push(`${indent} *
${CALLER}${COLON}${formatCallers(getCallers(className))}${PERIOD}`); modified = true; } } } out.push(line); } if (modified) { const text = out.join('\n'); fs.writeFileSync(filePath, text.endsWith('\n') ? text : text + '\n', 'utf8'); } return modified; } function walkJava(dir, out = []) { if (!fs.existsSync(dir)) return out; for (const ent of fs.readdirSync(dir, { withFileTypes: true })) { const p = path.join(dir, ent.name); if (ent.isDirectory()) walkJava(p, out); else if (ent.name.endsWith('.java') && ent.name !== 'package-info.java') out.push(p); } return out; } function collectFiles() { const files = []; for (const pkg of TARGET_PACKAGES) walkJava(path.join(WORKFLOW_DIR, pkg), files); for (const f of fs.readdirSync(WORKFLOW_DIR).filter(x => x.endsWith('.java') && x !== 'package-info.java')) { files.push(path.join(WORKFLOW_DIR, f)); } return files.sort(); } const modified = []; for (const fp of collectFiles()) { if (processFile(fp)) { modified.push(path.relative(ROOT, fp)); console.log('MODIFIED: ' + path.basename(fp)); } else { console.log('SKIP: ' + path.basename(fp)); } } console.log('\nModified: ' + modified.length);