add_workflow_subpkg_javadoc.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. #!/usr/bin/env node
  2. 'use strict';
  3. /**
  4. * Add JavaDoc to workflow subpackages. Documentation-only; no logic changes.
  5. * Safe mode: only inserts missing javadoc or adds 锟斤拷锟矫凤拷 line to existing blocks.
  6. */
  7. const fs = require('fs');
  8. const path = require('path');
  9. const ROOT = 'd:/ylrz_saas_new/java';
  10. const WORKFLOW_DIR = path.join(ROOT, 'fs-service/src/main/java/com/fs/company/service/workflow');
  11. const DATA = JSON.parse(fs.readFileSync(path.join(ROOT, 'scripts/workflow-subpkg-javadoc-data.json'), 'utf8'));
  12. const TARGET_PACKAGES = [
  13. 'api', 'async', 'cache', 'canvas', 'capability', 'data', 'dedup', 'event',
  14. 'expression', 'feedback', 'handoff', 'health', 'heartbeat', 'http', 'inbound',
  15. 'knowledge', 'media', 'memory', 'monitor', 'pay', 'push', 'queue', 'scope',
  16. 'sim', 'vector',
  17. ];
  18. const COLON = '\uFF1A';
  19. const PERIOD = '\u3002';
  20. const ENUM_SEP = '\u3001';
  21. const CALLER = DATA.callerLabel || '\u8c03\u7528\u65b9';
  22. const MAIN_CALLER = DATA.mainCallerLabel || '\u4e3b\u8981\u8c03\u7528\u65b9';
  23. const SPRING = DATA.springCaller || 'Spring \u5bb9\u5668\u6ce8\u5165';
  24. function getCallers(className) {
  25. if (DATA.callers && DATA.callers[className]) return DATA.callers[className];
  26. const keys = [className, className.replace(/Impl$/, '')];
  27. if (!keys[1].startsWith('I')) keys.push('I' + keys[1]);
  28. for (const k of keys) {
  29. if (DATA.callers && DATA.callers[k]) return DATA.callers[k];
  30. }
  31. return [SPRING];
  32. }
  33. function formatCallers(callers) {
  34. return callers.map(c => `{@code ${c}}`).join(ENUM_SEP);
  35. }
  36. function parseInterfaceMethods(className) {
  37. const iface = className.replace(/Impl$/, '');
  38. const candidates = [
  39. path.join(WORKFLOW_DIR, iface + '.java'),
  40. path.join(WORKFLOW_DIR, 'I' + iface + '.java'),
  41. ];
  42. const methods = {};
  43. for (const f of candidates) {
  44. if (!fs.existsSync(f)) continue;
  45. const text = fs.readFileSync(f, 'utf8');
  46. const re = /\/\*\*([\s\S]*?)\*\/\s*(?:@[\w.]+\s*)*[\w<>,\[\]\s.?]+\s+(\w+)\s*\([^)]*\)\s*;/g;
  47. let m;
  48. while ((m = re.exec(text))) {
  49. const first = m[1].split('\n').map(l => l.replace(/^\s*\*\s?/, '').trim())
  50. .find(l => l && !l.startsWith('@') && !l.startsWith('param') && !l.startsWith('return') && !l.includes(CALLER) && !l.includes(MAIN_CALLER));
  51. if (first) methods[m[2]] = first.replace(/\s*@param.*/, '').trim();
  52. }
  53. }
  54. return methods;
  55. }
  56. function methodPurpose(className, methodName, ifaceMethods) {
  57. if (ifaceMethods[methodName]) return ifaceMethods[methodName];
  58. if (DATA.methodCn && DATA.methodCn[methodName]) return DATA.methodCn[methodName];
  59. if (methodName.startsWith('get') && methodName.length > 3) return `\u83b7\u53d6${methodName.slice(3)}\u3002`;
  60. if (methodName.startsWith('set') && methodName.length > 3) return `\u8bbe\u7f6e${methodName.slice(3)}\u3002`;
  61. if (methodName.startsWith('is') && methodName.length > 2) return `\u5224\u65ad\u662f\u5426${methodName.slice(2)}\u3002`;
  62. if (methodName.startsWith('has') && methodName.length > 3) return `\u5224\u65ad\u662f\u5426\u5305\u542b${methodName.slice(3)}\u3002`;
  63. if (methodName.startsWith('list')) return `\u67e5\u8be2${methodName.slice(4)}\u5217\u8868\u3002`;
  64. if (methodName.startsWith('save') || methodName.startsWith('create')) return '\u4fdd\u5b58/\u521b\u5efa\u76f8\u5173\u6570\u636e\u3002';
  65. if (methodName.startsWith('delete') || methodName.startsWith('remove')) return '\u5220\u9664\u76f8\u5173\u6570\u636e\u3002';
  66. if (methodName.startsWith('update')) return `\u66f4\u65b0${methodName.slice(6)}\u3002`;
  67. return `${methodName} \u7684\u4e1a\u52a1\u5165\u53e3\u3002`;
  68. }
  69. function hasJavadocBefore(lines, idx) {
  70. let j = idx - 1;
  71. while (j >= 0 && (lines[j].trim().startsWith('@') || lines[j].trim() === '')) j--;
  72. return j >= 0 && lines[j].trim().endsWith('*/');
  73. }
  74. function hasCallerInJavadoc(lines, idx) {
  75. let j = idx - 1;
  76. while (j >= 0) {
  77. const t = lines[j].trim();
  78. if (t.startsWith('/**')) break;
  79. if (t.includes(CALLER) || t.includes(MAIN_CALLER)) return true;
  80. j--;
  81. }
  82. return false;
  83. }
  84. function makeMethodJavadoc(className, methodName, ifaceMethods, indent) {
  85. const purpose = methodPurpose(className, methodName, ifaceMethods);
  86. const callers = formatCallers(getCallers(className));
  87. return [
  88. `${indent}/**`,
  89. `${indent} * ${purpose}`,
  90. `${indent} * <p>${CALLER}${COLON}${callers}${PERIOD}`,
  91. `${indent} */`,
  92. ];
  93. }
  94. function makeClassJavadoc(className, isInterface) {
  95. const kind = isInterface ? '\u63a5\u53e3' : '\u670d\u52a1';
  96. const desc = (DATA.classDesc && DATA.classDesc[className]) || `${className}\uFF1A\u9f99\u867e workflow ${kind}\u3002`;
  97. const callers = formatCallers(getCallers(className));
  98. return ['/**', ` * ${desc}`, ` * <p>${MAIN_CALLER}${COLON}${callers}${PERIOD}`, ' */'];
  99. }
  100. function extractMethodName(lines, startIdx) {
  101. let combined = lines[startIdx].trim();
  102. let k = startIdx;
  103. while (!/[;{]/.test(combined) && k + 1 < lines.length) {
  104. k++;
  105. combined += ' ' + lines[k].trim();
  106. }
  107. const mm = combined.match(/\b(\w+)\s*\(/);
  108. if (!mm || ['if', 'for', 'while', 'switch', 'catch', 'synchronized', 'return', 'new', 'super'].includes(mm[1])) return null;
  109. return mm[1];
  110. }
  111. function findDeclIdx(lines, className) {
  112. return lines.findIndex(l =>
  113. new RegExp(`public\\s+(?:final\\s+)?(?:class|interface|enum)\\s+${className}\\b`).test(l));
  114. }
  115. function processFile(filePath) {
  116. const className = path.basename(filePath, '.java');
  117. if (className === 'package-info') return false;
  118. let lines = fs.readFileSync(filePath, 'utf8').split(/\n/);
  119. const ifaceMethods = parseInterfaceMethods(className);
  120. const declIdx = findDeclIdx(lines, className);
  121. if (declIdx < 0) return false;
  122. const isInterface = /public\s+interface\s+/.test(lines[declIdx]);
  123. let modified = false;
  124. let hasClassDoc = false;
  125. {
  126. let j = declIdx - 1;
  127. while (j >= 0 && (lines[j].trim().startsWith('@') || lines[j].trim() === '')) j--;
  128. if (j >= 0 && (lines[j].trim().startsWith('/**') || lines[j].trim().endsWith('*/'))) hasClassDoc = true;
  129. }
  130. if (!hasClassDoc) {
  131. let insertAt = declIdx;
  132. while (insertAt > 0 && (lines[insertAt - 1].trim().startsWith('@') || lines[insertAt - 1].trim() === '')) insertAt--;
  133. const classDoc = makeClassJavadoc(className, isInterface);
  134. lines.splice(insertAt, 0, ...classDoc);
  135. modified = true;
  136. } else if (!hasCallerInJavadoc(lines, declIdx)) {
  137. let j = declIdx - 1;
  138. while (j >= 0 && (lines[j].trim().startsWith('@') || lines[j].trim() === '')) j--;
  139. if (j >= 0 && lines[j].trim() === '*/') {
  140. const callers = formatCallers(getCallers(className));
  141. lines.splice(j, 0, ` * <p>${MAIN_CALLER}${COLON}${callers}${PERIOD}`);
  142. modified = true;
  143. }
  144. }
  145. const out = [];
  146. for (let i = 0; i < lines.length; i++) {
  147. const line = lines[i];
  148. const stripped = line.trim();
  149. if (stripped.startsWith('@') && i + 1 < lines.length) {
  150. let k = i;
  151. while (k < lines.length && lines[k].trim().startsWith('@')) k++;
  152. if (k < lines.length && /^public\s+/.test(lines[k].trim())) {
  153. if (!hasJavadocBefore(lines, i)) {
  154. const name = extractMethodName(lines, k);
  155. if (name) {
  156. const indent = (line.match(/^(\s*)/) || ['', ' '])[1];
  157. out.push(...makeMethodJavadoc(className, name, ifaceMethods, indent));
  158. modified = true;
  159. }
  160. } else if (!hasCallerInJavadoc(lines, i)) {
  161. let j = i - 1;
  162. while (j >= 0 && lines[j].trim() !== '*/') j--;
  163. if (j >= 0) {
  164. const indent = (lines[j].match(/^(\s*)/) || ['', ' '])[1];
  165. out.push(`${indent} * <p>${CALLER}${COLON}${formatCallers(getCallers(className))}${PERIOD}`);
  166. modified = true;
  167. }
  168. }
  169. while (i <= k) { out.push(lines[i]); i++; }
  170. i--;
  171. continue;
  172. }
  173. }
  174. if (/^public\s+(?!class\b|interface\b|enum\b|static\s+class\b)/.test(stripped)) {
  175. let prev = out.length - 1;
  176. while (prev >= 0 && out[prev].trim() === '') prev--;
  177. if (prev >= 0 && out[prev].trim().startsWith('@')) {
  178. out.push(line);
  179. continue;
  180. }
  181. if (!hasJavadocBefore(lines, i)) {
  182. const name = extractMethodName(lines, i);
  183. if (name) {
  184. const indent = (line.match(/^(\s*)/) || ['', ' '])[1];
  185. out.push(...makeMethodJavadoc(className, name, ifaceMethods, indent));
  186. modified = true;
  187. }
  188. } else if (!hasCallerInJavadoc(lines, i)) {
  189. let j = i - 1;
  190. while (j >= 0 && lines[j].trim() !== '*/') j--;
  191. if (j >= 0) {
  192. const indent = (lines[j].match(/^(\s*)/) || ['', ' '])[1];
  193. out.push(`${indent} * <p>${CALLER}${COLON}${formatCallers(getCallers(className))}${PERIOD}`);
  194. modified = true;
  195. }
  196. }
  197. }
  198. out.push(line);
  199. }
  200. if (modified) {
  201. const text = out.join('\n');
  202. fs.writeFileSync(filePath, text.endsWith('\n') ? text : text + '\n', 'utf8');
  203. }
  204. return modified;
  205. }
  206. function walkJava(dir, out = []) {
  207. if (!fs.existsSync(dir)) return out;
  208. for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
  209. const p = path.join(dir, ent.name);
  210. if (ent.isDirectory()) walkJava(p, out);
  211. else if (ent.name.endsWith('.java') && ent.name !== 'package-info.java') out.push(p);
  212. }
  213. return out;
  214. }
  215. function collectFiles() {
  216. const files = [];
  217. for (const pkg of TARGET_PACKAGES) walkJava(path.join(WORKFLOW_DIR, pkg), files);
  218. for (const f of fs.readdirSync(WORKFLOW_DIR).filter(x => x.endsWith('.java') && x !== 'package-info.java')) {
  219. files.push(path.join(WORKFLOW_DIR, f));
  220. }
  221. return files.sort();
  222. }
  223. const modified = [];
  224. for (const fp of collectFiles()) {
  225. if (processFile(fp)) {
  226. modified.push(path.relative(ROOT, fp));
  227. console.log('MODIFIED: ' + path.basename(fp));
  228. } else {
  229. console.log('SKIP: ' + path.basename(fp));
  230. }
  231. }
  232. console.log('\nModified: ' + modified.length);