Skip to content

Commit f178c70

Browse files
committed
🔨 仮でjsdocからmd生成機を作成
1 parent 1fd66e8 commit f178c70

File tree

7 files changed

+351
-18
lines changed

7 files changed

+351
-18
lines changed

dev/build.js

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const { execSync } = require("node:child_process");
1111
const generateIndex = require("./build/generateIndex");
1212
const createEntryEndpoint = require("./build/createEntryEndpoint");
1313
const checkIllegalStrings = require("./build/checkIllegalStrings");
14+
const generateMdClass = require("./build/generateMdClass");
1415
const CL = require("./libs/ColorLogger");
1516

1617
const script_name = "JavaLibraryScript";
@@ -23,6 +24,7 @@ const entryDir = path.join(baseDir, "src");
2324
const distDir = path.join(baseDir, "dist");
2425
const typesTmpDir = path.join(baseDir, "dev/tmp/types");
2526
const typesDir = path.join(baseDir, "types");
27+
const docsDir = path.join(baseDir, "docs");
2628

2729
const entryPath = path.join(entryDir, `${entry_name}.js`);
2830
const bundlePath = path.join(distDir, `${script_name}.js`);
@@ -32,6 +34,8 @@ const minMapPath = path.join(distDir, `${script_name}.min.js.map`);
3234
const entryTypesPath = path.join(typesTmpDir, `${type_entry_name}.d.ts`);
3335
const typesPath = path.join(typesDir, `${script_name}.d.ts`);
3436

37+
const mdCodeDocsPath = path.join(docsDir, "code.md");
38+
3539
// 相対座標を取得
3640
function getRelativePath(name) {
3741
return path.relative(baseDir, name);
@@ -172,48 +176,52 @@ function fixDtsOutputFlexible(filePath, log = false) {
172176
generateIndex(entryDir, logView);
173177
console.log(`┃┗🌱 ${CL.brightWhite("自動生成完了")}`);
174178
//
175-
console.log(`🗑️ ${CL.brightWhite("distフォルダリセット")}`);
179+
console.log(`🗑️ ${CL.brightWhite("distフォルダリセット")}`);
176180
prepareDir(distDir);
177181
//
178-
console.log(`🗂️ ${CL.brightWhite("バンドル中...")}`);
182+
console.log(`🗂️ ${CL.brightWhite("バンドル中...")}`);
179183
const code = await bundle();
180184
console.log(`┃┣✅ ${CL.brightWhite("バンドル完了")}: ${getRelativePath(bundlePath)}`);
181185
console.log(`┃┗🗺️ ${CL.brightWhite("ソースマップ生成")}: ${getRelativePath(bundleMapPath)}`);
182186
//
183-
console.log(`🔧 ${CL.brightWhite("Minify中...")}`);
187+
console.log(`🔧 ${CL.brightWhite("Minify中...")}`);
184188
await minifyCode(code);
185189
console.log(`┃┣✅ ${CL.brightWhite("Minify完了:")} ${getRelativePath(minPath)}`);
186190
console.log(`┃┗🗺️ ${CL.brightWhite("ソースマップ生成[min]")}: ${getRelativePath(minMapPath)}`);
187191
showFileSize(bundlePath);
188192
showFileSize(minPath);
189193
//
190194
if (debug) {
191-
console.log(`🗑️ ${CL.brightWhite("types仮フォルダリセット")}`);
195+
console.log(`🗑️ ${CL.brightWhite("types仮フォルダリセット")}`);
192196
prepareDir(typesTmpDir);
193-
console.log(`🗒️ ${CL.brightWhite("TypeScriptコンパイル中...")}`);
197+
console.log(`🗒️ ${CL.brightWhite("TypeScriptコンパイル中...")}`);
194198
execSync("npx tsc", { stdio: "inherit" });
195199
console.log(`┃┗✅ ${CL.brightWhite("TypeScriptコンパイル完了")}: ${getRelativePath(typesTmpDir)}`);
196-
console.log(`${CL.brightWhite("rollup用entrypoint作成")}`);
200+
console.log(`${CL.brightWhite("rollup用entrypoint作成")}`);
197201
createEntryEndpoint(entryTypesPath);
198-
console.log("📦 .d.ts を rollup中...");
202+
console.log("📦 .d.ts を rollup中...");
199203
await buildRollup();
200204
console.log(`┃┗✅ ${CL.brightWhite("rollup完了")}: ${getRelativePath(typesPath)}`);
201-
console.log(`🗑️ ${CL.brightWhite("types仮フォルダcleanup")}`);
205+
console.log(`🗑️ ${CL.brightWhite("types仮フォルダcleanup")}`);
202206
prepareDir(typesTmpDir);
203-
console.log(`🌵 ${CL.brightWhite("予測候補・コンパイル問題を解決")}`);
207+
console.log(`🌵 ${CL.brightWhite("予測候補・コンパイル問題を解決")}`);
204208
fixDtsOutputFlexible(typesPath, logView);
205209
console.log(`┃┗✅ ${CL.brightWhite("予測候補・コンパイル問題 修正完了")}: ${getRelativePath(typesPath)}`);
206210
showFileSize(typesPath);
207211
}
208212

209-
console.log(`🔍 ${CL.brightWhite("問題性の高い文字列の検査を開始...")}`);
213+
console.log(`🔍 ${CL.brightWhite("問題性の高い文字列の検査を開始...")}`);
210214
const illegalFound = checkIllegalStrings(baseDir);
211215
if (illegalFound) {
212216
console.log(`┃┗❌ ${CL.brightWhite("検査完了")} ${CL.red("(違法文字列発見)")}`);
213217
} else {
214218
console.log(`┃┗✅ ${CL.brightWhite("検査完了")}`);
215219
}
216220

221+
console.log(`┣ℹ️ ${CL.brightWhite("mdのコンテンツ生成中...")}`);
222+
generateMdClass(entryDir, mdCodeDocsPath);
223+
console.log(`┃┗✅ ${CL.brightWhite("mdのコンテンツ生成完了")}: ${mdCodeDocsPath}`);
224+
217225
const end = performance.now() - start;
218226
console.log(`┣🕒 ${CL.brightWhite("ビルド時間")}: ${CL.brightGreen(end.toFixed(2))} ms`);
219227
console.log(`┗🎉 ${CL.brightYellow("ビルド完了")}`);

dev/build/generateMdClass.js

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
const fs = require("node:fs");
2+
const path = require("node:path");
3+
4+
/**
5+
* 指定ディレクトリ以下の全JSファイルからクラス定義とJSDocを抽出してMarkdown生成
6+
* @param {string} baseDir - 探索開始ディレクトリ(例: ./src)
7+
* @param {string} outputPath - 出力先Markdownファイル(例: ./docs/classes.md)
8+
*/
9+
function generateMarkdownFromClasses(baseDir, outputPath) {
10+
const allJsFiles = [];
11+
walkDir(baseDir, allJsFiles);
12+
13+
let markdown = `# クラス一覧\n\n`;
14+
15+
for (const filePath of allJsFiles) {
16+
const relPath = path.relative(baseDir, filePath);
17+
const content = fs.readFileSync(filePath, "utf-8");
18+
const classBlocks = extractClassDocs(content);
19+
if (classBlocks.length === 0) continue;
20+
21+
markdown += `## ${relPath}\n\n`;
22+
for (const block of classBlocks) {
23+
const typeParams = block.templates.length > 0 ? `<${block.templates.join(", ")}>` : "";
24+
const paramList = block.params.map((p) => `${p.name}: ${p.type}`).join(", ");
25+
markdown += `### ${block.name}\n\n`;
26+
markdown += `\`\`\`ts\nclass ${block.name}${typeParams}(${paramList}): ${block.name}${typeParams}\n\`\`\`\n\n`;
27+
markdown += `${block.description || "説明なし"}\n\n`;
28+
}
29+
}
30+
31+
fs.writeFileSync(outputPath, markdown, "utf-8");
32+
}
33+
34+
/**
35+
* ディレクトリを再帰的に探索してJSファイルを収集
36+
* @param {string} dir
37+
* @param {string[]} fileList
38+
*/
39+
function walkDir(dir, fileList) {
40+
const entries = fs.readdirSync(dir, { withFileTypes: true });
41+
for (const entry of entries) {
42+
const fullPath = path.join(dir, entry.name);
43+
if (entry.isDirectory()) {
44+
walkDir(fullPath, fileList);
45+
} else if (entry.isFile() && entry.name.endsWith(".js")) {
46+
fileList.push(fullPath);
47+
}
48+
}
49+
}
50+
51+
/**
52+
* クラス定義と直前のJSDocコメントを抽出(複数行のみ)を抽出
53+
* @param {string} code - ファイルの内容
54+
* @returns {{ name: string, description: string, templates: string[], params: string[] }[]}
55+
*/
56+
function extractClassDocs(code) {
57+
const classRegex = /\/\*\*((?:\s*\*(?:(?!\/\*\*)[\s\S])*?)\n\s*)\*\/\s*class\s+(\w+)/g;
58+
const results = [];
59+
60+
let match;
61+
while ((match = classRegex.exec(code)) !== null) {
62+
const [fullMatch, commentBody, className] = match;
63+
const classStartIdx = match.index;
64+
65+
// 1行JSDoc(/** ... */)は除外
66+
const isSingleLine = fullMatch.split("\n").length === 1;
67+
if (isSingleLine) continue;
68+
69+
const desc = parseJsDocDescription(commentBody);
70+
const templates = parseJsDocTemplates(commentBody);
71+
72+
const constructorInfo = extractConstructorParams(code.slice(classStartIdx));
73+
74+
results.push({ name: className, description: desc, templates, params: constructorInfo.params });
75+
}
76+
77+
return results;
78+
}
79+
80+
/**
81+
* JSDocコメントから説明部分を抽出(@description 優先)
82+
* @param {string} comment - JSDoc コメント
83+
* @returns {string}
84+
*/
85+
function parseJsDocDescription(comment) {
86+
const lines = comment.split("\n").map((line) => line.replace(/^\s*\*\s?/, "").trim());
87+
const descriptionLine = lines.find((line) => line.startsWith("@description"));
88+
if (descriptionLine) return descriptionLine.replace("@description", "").trim();
89+
90+
// @descriptionが無ければ最初の行を説明とみなす
91+
for (const line of lines) {
92+
if (line && !line.startsWith("@")) return line;
93+
}
94+
95+
return "";
96+
}
97+
98+
function parseJsDocTemplates(comment) {
99+
const lines = comment.split("\n");
100+
const templateRegex = /@template\s+(\w+)/;
101+
const templates = [];
102+
for (const line of lines) {
103+
const match = line.match(templateRegex);
104+
if (match) templates.push(match[1]);
105+
}
106+
return templates;
107+
}
108+
109+
function extractConstructorParams(classBody) {
110+
const constructorRegex = /\/\*\*((?:\s*\*(?!\/)[\s\S]*?)\*\/)\s*constructor\s*\(([^\)]*)\)/;
111+
const match = classBody.match(constructorRegex);
112+
113+
if (!match) return { params: [] };
114+
115+
const jsdoc = match[1];
116+
return {
117+
params: parseJsDocParams(jsdoc),
118+
};
119+
}
120+
121+
function parseJsDocParams(comment) {
122+
const lines = comment.split("\n");
123+
const paramRegex = /@param\s+{([^}]+)}\s+(\w+)/;
124+
const params = [];
125+
for (const line of lines) {
126+
const match = line.match(paramRegex);
127+
if (match) {
128+
const [, type, name] = match;
129+
params.push({ name, type });
130+
}
131+
}
132+
return params;
133+
}
134+
135+
module.exports = generateMarkdownFromClasses;

dist/JavaLibraryScript.js

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/JavaLibraryScript.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)