add_impl_javadoc.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. #!/usr/bin/env node
  2. 'use strict';
  3. const fs = require('fs');
  4. const path = require('path');
  5. const ROOT = 'd:/ylrz_saas_new/java';
  6. const IMPL_DIR = path.join(ROOT, 'fs-service/src/main/java/com/fs/company/service/workflow/impl');
  7. const WORKFLOW_DIR = path.join(ROOT, 'fs-service/src/main/java/com/fs/company/service/workflow');
  8. const COLON = '\uFF1A';
  9. const PERIOD = '\u3002';
  10. const ENUM_SEP = '\u3001';
  11. const STRINGS = JSON.parse(fs.readFileSync(path.join(ROOT, 'scripts/javadoc-strings.json'), 'utf8'));
  12. function getCallers(className) {
  13. const keys = [className, className.replace(/Impl$/, '')];
  14. if (!keys[1].startsWith('I')) keys.push('I' + keys[1]);
  15. for (const k of keys) {
  16. if (STRINGS.callers[k]) return STRINGS.callers[k];
  17. }
  18. return [STRINGS.springCaller || 'Spring \u5bb9\u5668\u6ce8\u5165\u8c03\u7528'];
  19. }
  20. function formatCallers(callers) {
  21. return callers.map(c => `{@code ${c}}`).join(ENUM_SEP);
  22. }
  23. function parseInterfaceMethods(className) {
  24. const iface = className.replace(/Impl$/, '');
  25. const candidates = [
  26. path.join(WORKFLOW_DIR, iface + '.java'),
  27. path.join(WORKFLOW_DIR, 'I' + iface + '.java'),
  28. ];
  29. const methods = {};
  30. for (const f of candidates) {
  31. if (!fs.existsSync(f)) continue;
  32. const text = fs.readFileSync(f, 'utf8');
  33. const re = /\/\*\*([\s\S]*?)\*\/\s*(?:@[\w.]+\s*)*[\w<>,\[\]\s.?]+\s+(\w+)\s*\([^)]*\)\s*;/g;
  34. let m;
  35. while ((m = re.exec(text))) {
  36. const first = m[1].split('\n').map(l => l.replace(/^\s*\*\s?/, '').trim())
  37. .find(l => l && !l.startsWith('@') && !l.startsWith('param') && !l.startsWith('return'));
  38. if (first) methods[m[2]] = first.replace(/\s*@param.*/, '').trim();
  39. }
  40. }
  41. return methods;
  42. }
  43. function methodPurpose(methodName, ifaceMethods) {
  44. if (ifaceMethods[methodName]) return ifaceMethods[methodName];
  45. if (methodName.startsWith('get') && methodName.length > 3) return `\u83b7\u53d6${methodName.slice(3)}\u3002`;
  46. if (methodName.startsWith('set') && methodName.length > 3) return `\u8bbe\u7f6e${methodName.slice(3)}\u3002`;
  47. if (methodName.startsWith('is') && methodName.length > 2) return `\u5224\u65ad\u662f\u5426${methodName.slice(2)}\u3002`;
  48. if (methodName.startsWith('list')) return `\u67e5\u8be2${methodName.slice(4)}\u5217\u8868\u3002`;
  49. if (methodName.startsWith('save') || methodName.startsWith('create')) return '\u4fdd\u5b58/\u521b\u5efa\u76f8\u5173\u6570\u636e\u3002';
  50. if (methodName.startsWith('delete') || methodName.startsWith('remove')) return '\u5220\u9664\u76f8\u5173\u6570\u636e\u3002';
  51. if (methodName.startsWith('update')) return `\u66f4\u65b0${methodName.slice(6)}\u3002`;
  52. if (methodName.startsWith('batch')) return `\u6279\u91cf${methodName.slice(5)}\u3002`;
  53. if (methodName.startsWith('run') || methodName.startsWith('test')) return '\u8fd0\u884c\u6d4b\u8bd5\u6216\u4efb\u52a1\u3002';
  54. return `${methodName} \u7684\u516c\u5171\u5165\u53e3\u3002`;
  55. }
  56. function hasJavadocBefore(lines, idx) {
  57. let j = idx - 1;
  58. while (j >= 0 && lines[j].trim() === '') j--;
  59. return j >= 0 && lines[j].trim().endsWith('*/');
  60. }
  61. function hasCallerInJavadoc(lines, idx) {
  62. let j = idx - 1;
  63. while (j >= 0) {
  64. const t = lines[j].trim();
  65. if (t.startsWith('/**')) break;
  66. if (t.includes(STRINGS.callerLabel) || t.includes(STRINGS.mainCallerLabel)) return true;
  67. j--;
  68. }
  69. return false;
  70. }
  71. function makeMethodJavadoc(className, methodName, ifaceMethods, indent) {
  72. const purpose = methodPurpose(methodName, ifaceMethods);
  73. const callers = formatCallers(getCallers(className));
  74. return [
  75. `${indent}/**`,
  76. `${indent} * ${purpose}`,
  77. `${indent} * <p>${STRINGS.callerLabel}${COLON}${callers}${PERIOD}`,
  78. `${indent} */`,
  79. ];
  80. }
  81. function makeClassJavadoc(className) {
  82. const desc = STRINGS.classDesc[className] || `${className}\uFF1Aworkflow impl \u5305 Spring \u670d\u52a1\u5b9e\u73b0\u3002`;
  83. const callers = formatCallers(getCallers(className));
  84. return ['/**', ` * ${desc}`, ` * <p>${STRINGS.mainCallerLabel}${COLON}${callers}${PERIOD}`, ' */'];
  85. }
  86. function extractMethodName(lines, startIdx) {
  87. let combined = lines[startIdx].trim();
  88. let k = startIdx;
  89. while (!/[;{]/.test(combined) && k + 1 < lines.length) {
  90. k++;
  91. combined += ' ' + lines[k].trim();
  92. }
  93. const mm = combined.match(/\b(\w+)\s*\(/);
  94. if (!mm || ['if', 'for', 'while', 'switch', 'catch', 'synchronized'].includes(mm[1])) return null;
  95. return mm[1];
  96. }
  97. function processFile(filePath) {
  98. const className = path.basename(filePath, '.java');
  99. if (className === 'package-info') return false;
  100. let lines = fs.readFileSync(filePath, 'utf8').split(/\n/);
  101. const ifaceMethods = parseInterfaceMethods(className);
  102. let classIdx = lines.findIndex(l => new RegExp(`public\\s+(?:final\\s+)?class\\s+${className}\\b`).test(l));
  103. if (classIdx < 0) return false;
  104. let modified = false;
  105. let hasClassDoc = false;
  106. {
  107. let j = classIdx - 1;
  108. while (j >= 0 && (lines[j].trim().startsWith('@') || lines[j].trim() === '')) j--;
  109. if (j >= 0 && (lines[j].trim().startsWith('/**') || lines[j].trim().endsWith('*/'))) hasClassDoc = true;
  110. }
  111. if (!hasClassDoc) {
  112. let insertAt = classIdx;
  113. while (insertAt > 0 && (lines[insertAt - 1].trim().startsWith('@') || lines[insertAt - 1].trim() === '')) insertAt--;
  114. const classDoc = makeClassJavadoc(className);
  115. lines.splice(insertAt, 0, ...classDoc);
  116. classIdx += classDoc.length;
  117. modified = true;
  118. } else if (!hasCallerInJavadoc(lines, classIdx)) {
  119. let j = classIdx - 1;
  120. while (j >= 0 && (lines[j].trim().startsWith('@') || lines[j].trim() === '')) j--;
  121. if (j >= 0 && lines[j].trim() === '*/') {
  122. const callers = formatCallers(getCallers(className));
  123. lines.splice(j, 0, ` * <p>${STRINGS.mainCallerLabel}${COLON}${callers}${PERIOD}`);
  124. modified = true;
  125. }
  126. }
  127. const out = [];
  128. for (let i = 0; i < lines.length; i++) {
  129. const line = lines[i];
  130. const stripped = line.trim();
  131. // Method with one or more leading annotations (@Override, @Transactional, ...)
  132. if (stripped.startsWith('@') && i + 1 < lines.length) {
  133. let k = i;
  134. while (k < lines.length && lines[k].trim().startsWith('@')) k++;
  135. if (k < lines.length && /^public\s+/.test(lines[k].trim())) {
  136. if (!hasJavadocBefore(lines, i)) {
  137. const name = extractMethodName(lines, k);
  138. if (name) {
  139. const indent = (line.match(/^(\s*)/) || ['', ' '])[1];
  140. out.push(...makeMethodJavadoc(className, name, ifaceMethods, indent));
  141. modified = true;
  142. }
  143. } else if (!hasCallerInJavadoc(lines, i)) {
  144. let j = i - 1;
  145. while (j >= 0 && lines[j].trim() !== '*/') j--;
  146. if (j >= 0) {
  147. const indent = (lines[j].match(/^(\s*)/) || ['', ' '])[1];
  148. out.push(`${indent} * <p>${STRINGS.callerLabel}${COLON}${formatCallers(getCallers(className))}${PERIOD}`);
  149. modified = true;
  150. }
  151. }
  152. while (i <= k) {
  153. out.push(lines[i]);
  154. i++;
  155. }
  156. i--;
  157. continue;
  158. }
  159. }
  160. if (/^public\s+(?!class\b|interface\b|enum\b|static\s+class\b)/.test(stripped) && !stripped.startsWith('@')) {
  161. let prev = out.length - 1;
  162. while (prev >= 0 && out[prev].trim() === '') prev--;
  163. if (prev >= 0 && out[prev].trim().startsWith('@')) {
  164. out.push(line);
  165. continue;
  166. }
  167. if (!hasJavadocBefore(lines, i)) {
  168. const name = extractMethodName(lines, i);
  169. if (name) {
  170. const indent = (line.match(/^(\s*)/) || ['', ' '])[1];
  171. out.push(...makeMethodJavadoc(className, name, ifaceMethods, indent));
  172. modified = true;
  173. }
  174. } else if (!hasCallerInJavadoc(lines, i)) {
  175. let j = i - 1;
  176. while (j >= 0 && lines[j].trim() !== '*/') j--;
  177. if (j >= 0) {
  178. const indent = (lines[j].match(/^(\s*)/) || ['', ' '])[1];
  179. out.push(`${indent} * <p>${STRINGS.callerLabel}${COLON}${formatCallers(getCallers(className))}${PERIOD}`);
  180. modified = true;
  181. }
  182. }
  183. }
  184. out.push(line);
  185. }
  186. if (modified) {
  187. const text = out.join('\n');
  188. fs.writeFileSync(filePath, text.endsWith('\n') ? text : text + '\n', 'utf8');
  189. }
  190. return modified;
  191. }
  192. const modified = [];
  193. for (const f of fs.readdirSync(IMPL_DIR).filter(x => x.endsWith('.java')).sort()) {
  194. const fp = path.join(IMPL_DIR, f);
  195. if (processFile(fp)) {
  196. modified.push(f);
  197. console.log('MODIFIED: ' + f);
  198. } else {
  199. console.log('SKIP: ' + f);
  200. }
  201. }
  202. console.log('\nTotal modified: ' + modified.length);