#!/usr/bin/env node 'use strict'; const fs = require('fs'); const path = require('path'); const ROOT = 'd:/ylrz_saas_new/java'; const IMPL_DIR = path.join(ROOT, 'fs-service/src/main/java/com/fs/company/service/workflow/impl'); const WORKFLOW_DIR = path.join(ROOT, 'fs-service/src/main/java/com/fs/company/service/workflow'); const COLON = '\uFF1A'; const PERIOD = '\u3002'; const ENUM_SEP = '\u3001'; const STRINGS = JSON.parse(fs.readFileSync(path.join(ROOT, 'scripts/javadoc-strings.json'), 'utf8')); function getCallers(className) { const keys = [className, className.replace(/Impl$/, '')]; if (!keys[1].startsWith('I')) keys.push('I' + keys[1]); for (const k of keys) { if (STRINGS.callers[k]) return STRINGS.callers[k]; } return [STRINGS.springCaller || 'Spring \u5bb9\u5668\u6ce8\u5165\u8c03\u7528']; } 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')); if (first) methods[m[2]] = first.replace(/\s*@param.*/, '').trim(); } } return methods; } function methodPurpose(methodName, ifaceMethods) { if (ifaceMethods[methodName]) return ifaceMethods[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('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`; if (methodName.startsWith('batch')) return `\u6279\u91cf${methodName.slice(5)}\u3002`; if (methodName.startsWith('run') || methodName.startsWith('test')) return '\u8fd0\u884c\u6d4b\u8bd5\u6216\u4efb\u52a1\u3002'; return `${methodName} \u7684\u516c\u5171\u5165\u53e3\u3002`; } function hasJavadocBefore(lines, idx) { let j = idx - 1; while (j >= 0 && 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(STRINGS.callerLabel) || t.includes(STRINGS.mainCallerLabel)) return true; j--; } return false; } function makeMethodJavadoc(className, methodName, ifaceMethods, indent) { const purpose = methodPurpose(methodName, ifaceMethods); const callers = formatCallers(getCallers(className)); return [ `${indent}/**`, `${indent} * ${purpose}`, `${indent} *

${STRINGS.callerLabel}${COLON}${callers}${PERIOD}`, `${indent} */`, ]; } function makeClassJavadoc(className) { const desc = STRINGS.classDesc[className] || `${className}\uFF1Aworkflow impl \u5305 Spring \u670d\u52a1\u5b9e\u73b0\u3002`; const callers = formatCallers(getCallers(className)); return ['/**', ` * ${desc}`, ` *

${STRINGS.mainCallerLabel}${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'].includes(mm[1])) return null; return mm[1]; } 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); let classIdx = lines.findIndex(l => new RegExp(`public\\s+(?:final\\s+)?class\\s+${className}\\b`).test(l)); if (classIdx < 0) return false; let modified = false; let hasClassDoc = false; { let j = classIdx - 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 = classIdx; while (insertAt > 0 && (lines[insertAt - 1].trim().startsWith('@') || lines[insertAt - 1].trim() === '')) insertAt--; const classDoc = makeClassJavadoc(className); lines.splice(insertAt, 0, ...classDoc); classIdx += classDoc.length; modified = true; } else if (!hasCallerInJavadoc(lines, classIdx)) { let j = classIdx - 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, ` *

${STRINGS.mainCallerLabel}${COLON}${callers}${PERIOD}`); modified = true; } } const out = []; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const stripped = line.trim(); // Method with one or more leading annotations (@Override, @Transactional, ...) 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} *

${STRINGS.callerLabel}${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) && !stripped.startsWith('@')) { 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} *

${STRINGS.callerLabel}${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; } const modified = []; for (const f of fs.readdirSync(IMPL_DIR).filter(x => x.endsWith('.java')).sort()) { const fp = path.join(IMPL_DIR, f); if (processFile(fp)) { modified.push(f); console.log('MODIFIED: ' + f); } else { console.log('SKIP: ' + f); } } console.log('\nTotal modified: ' + modified.length);