| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- #!/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} * <p>${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}`, ` * <p>${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, ` * <p>${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} * <p>${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} * <p>${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);
|